ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

并发编程(五):Atomic——语言层的原子性、可见性与有序性

并发编程(五):Atomic——语言层的原子性、可见性与有序性 目录0. 从上一篇继续1. 三种语言的 Atomic 规则分别保证什么1.1 JavaAtomicInteger1.2 Gosync/atomic1.3 CPython应用层没有对称的 Atomic API2. 下一篇Atomic 是怎么实现的0. 从上一篇继续这一篇直接从语言内存模型层的规则讨论 Atomic。1. 三种语言的 Atomic 规则分别保证什么1.1 JavaAtomicInteger1.1.1 AtomicityAtomicInteger.incrementAndGet()原文“Atomically increments the current value, with memory effects as specified byVarHandle.getAndAdd.”这句话直接说明incrementAndGet()是一次原子加一。读取旧值、加一、写回不是三个可以被其他线程插入的步骤而是一次不可分割的 Atomic RMW。例如AtomicIntegercounternewAtomicInteger(0);// Thread Acounter.incrementAndGet();// Thread Bcounter.incrementAndGet();两次更新会依次作用在同一个值上Thread A Thread B incrementAndGet() incrementAndGet() │ │ ▼ ▼ 0 → 1 1 → 2因此不会出现普通counter中两个线程都读到0最后都写回1的丢失更新。1.1.2 VisibilityincrementAndGet()的内存效果由VarHandle.getAndAdd定义。VarHandle.getAndAdd 对写入的描述是“with the memory semantics ofsetVolatile(Object...)”而AtomicInteger.get()使用的是VarHandle.getVolatile的读语义。JLS §17.4.4 Synchronization Order 对 volatile 的规则是“synchronizes-with all subsequent reads of v by any thread”意思是Atomic 更新中的 volatile 写与另一个线程后续的 volatile 读之间可以建立synchronizes-with关系。放回前面的例子AtomicIntegercounternewAtomicInteger(0);booleanreadyfalse;// Thread Areadytrue;// A1counter.incrementAndGet();// A2// Thread Bif(counter.get()1){// B1System.out.println(ready);// B2}假设 B 的counter.get()读到的1就是 A 这次更新产生的结果那么Thread A Thread B A1 ready true │ │ program order ▼ A2 counter.incrementAndGet() │ │ synchronizes-with │ JLS §17.4.4 └──────────────────────────► B1 counter.get() 1 │ │ program order ▼ B2 read ready因此A 在 Atomic 更新之前已经完成的ready true对 B 后续的读取可见。这就是这里的Visibility。1.1.3 OrderingJLS §17.4.5 Happens-before Order 原文“If one action happens-before another, then the first is visible to and ordered before the second.”意思是happens-before 不只说明“能看见”还规定了两个操作之间必须遵守的先后关系。上面的例子可以直接推成A1 ready true ↓ program order A2 counter.incrementAndGet() ↓ synchronizes-with B1 counter.get() 1 ↓ program order B2 read ready也就是A1 happens-before A2 A2 happens-before B1 B1 happens-before B2 ↓ transitivity A1 happens-before B2所以 B 一旦已经通过counter.get()观察到 A 的更新就不能再出现counter 1 ready false这就是这里的Ordering。1.2 Gosync/atomic1.2.1 Atomicityatomic.Int64.Add原文“Add atomically adds delta to x and returns the new value.”意思很直接Add(1)把读取、加一和写回作为一次原子更新。例如varcounter atomic.Int64// Goroutine Acounter.Add(1)// Goroutine Bcounter.Add(1)两次更新不会丢掉其中一次Goroutine A Goroutine B Add(1) Add(1) │ │ ▼ ▼ 0 → 1 1 → 2这就是Atomicity。1.2.2 VisibilityThe Go Memory Model - Atomic Values 原文“If the effect of an atomic operation A is observed by atomic operation B, then A is synchronized before B.”意思是如果 B 的 Atomic 操作观察到了 A 的 Atomic 更新那么 A 和 B 之间就建立了synchronized-before关系。继续使用ready countervarcounter atomic.Int64varreadybool// Goroutine Areadytrue// A1counter.Add(1)// A2// Goroutine Bifcounter.Load()1{// B1fmt.Println(ready)// B2}假设 B 的counter.Load()读到的1就是 A 的Add(1)产生的结果Goroutine A Goroutine B A1 ready true │ │ sequenced-before ▼ A2 counter.Add(1) │ │ synchronized-before │ Go Memory Model └──────────────────────────► B1 counter.Load() 1 │ │ sequenced-before ▼ B2 read ready因此A 在 Atomic 更新之前写入的ready true对 B 后续的读取可见。这就是Visibility。1.2.3 Ordering同一节还规定所有 Atomic 操作表现得像处在“some sequentially consistent order”Go Memory Model 又把 happens-before 定义为sequenced-before和synchronized-before的传递闭包。因此上面的关系可以直接推成A1 ready true ↓ sequenced-before A2 counter.Add(1) ↓ synchronized-before B1 counter.Load() 1 ↓ sequenced-before B2 read ready也就是A1 happens-before A2 A2 happens-before B1 B1 happens-before B2 ↓ transitivity A1 happens-before B2所以 B 已经观察到counter 1后不能仍然把 A 在此之前的ready true当作没有发生。这就是Ordering。1.3 CPython应用层没有对称的 Atomic APIPython 标准库目前没有与 JavaAtomicInteger、Goatomic.Int64对称的通用整数 Atomic API。因此Python 应用代码没有一组可以像 Java、Go 那样直接引用的 Atomic 规则。普通的counter 1不是 Python 语言层定义的 Atomic API也不能因为某个 CPython 配置存在 GIL或者 Runtime 内部使用_Py_atomic_*就把它当作应用层可以依赖的跨实现 Atomicity、Visibility 或 Ordering 保证。CPython Runtime 确实需要 Atomic 来同步自己的共享状态。它如何把_Py_atomic_*落到编译器和 CPU是下一篇的实现问题而不是 Python 应用层公开语义的一部分。2. 下一篇Atomic 是怎么实现的这一篇停在语言层Java 和 Go 分别通过公开 Atomic API 与内存模型定义 Atomicity、Visibility 和 OrderingPython 应用层则没有对称的通用 Atomic API。下一篇会把这些语言层规则转换成实现层真正需要解决的问题分别沿着 JavaAtomicInteger、Gosync/atomic和 CPython Runtime 内部的_Py_atomic_*从 Runtime 一直看到 CPU。本文首发于 ThinkerQAQ 的个人博客由作者本人同步发布。原文可能持续修订最新版本请以个人博客为准。
返回列表