Acquire and Release Semantics

来源:互联网 发布:淘宝网上怎么进货 编辑:程序博客网 时间:2024/04/29 17:54
SEP 13, 2012

http://preshing.com/20120913/acquire-and-release-semantics/


通常说来,在lock-free编程中,线程之间有两种方式可以操作共享内存:它们可以竞争同一个资源,或者相互协作的从一个向另一个传递信息。Acquire and Release语义对后者是至关重要的:在线程之间可靠的传输信息。事实上,我冒昧的猜测,不正确的Acqure and Release语义或者丢失是lock-free编程中的头号错误。

我会实例说明如何用C++实现Acquire and Release语义的几种方法。我会介绍C++11 atomic库标准,所以你也不需要现在就了解它。需要说明的一点是,这里所说的都是关于没有sequential consistency下的lock-free编程。我们会直接处理multicore或者multiprocessor环境下的memory ordering。

不幸的是,acquire and release语义看起来比lock-free还要难以定义,你看到的web文章越多,你发现表面上相互矛盾的定义也就越多。Bruce Dawson提供了一个比较好的定义(归功于Herb Sutter),这是我自己的定义,接近C++11 atomic的原则:

Acquire semantics is a property which can only apply to operations which read from shared memory, whether they are read-modify-write operations or plain loads. The operation is then considered a read-acquire. Acquire semantics prevent memory reordering of the read-acquire with any read or write operation which follows it in program order.


翻译:
Aquire semantic只能应用在读共享内存的操作上,不管是read-modify-write还是load。这个操作就被认为是read-acquire。Acquire语义防止read-acquire和程序上在它之后的任何读写操作的内存乱序;


Release semantics is a property which can only apply to operations which write to shared memory, whether they are read-modify-write operations or plain stores. The operation is then considered a write-release. Release semantics prevent memory reordering of the write-release with any read or write operation which precedes it in program order.

翻译:Release semantics只能应用在写共享内存的操作上,不管是read-modify-write还是store。这个操作就被认为是write-release。Release语义防止write-release和程序上在它之前的任何读写操作的内存乱序;
一旦你消化了上面的定义,不难看到Acquire和Release语义可以使用我们前面介绍的几种memory barrier的简单组合来实现。这些barrier必须放在read-acquire操作之后,write-release之前。【注意,对于单个memory操作,这些barrier技术上要比acquire和release语义更加严格,但是它们是达到了期望的效果】

比较酷的就是acquire和release都不需要用到#StoreLoad barrier,它通常是一个比较昂贵的memory barrier。比如在PowerPC上,lwsync(lightweight sync)同时是三种#LoadLoad,#LoadStore和#StoreStore barrier,但是依然比sync指令快,因为sync包含了一个#StoreLoad barrier。

With Explicit Platform-Specific Fence Instructions

一种获得指定memory barrier的方式就是显式的使用fence指令。用简单的例子作为开始。比如我们在PowerPC上编程,__lwsync()就是个生成lwsync指令的编译器内部函数。因为lwsync提供了多种barrier,在下面的代码中中我们可以使用它达到acquire和release的语义。在Thread1,对Ready的store变成了write-release,在Thread2,对Ready的load变成了read-acquire。

如果我们让两个线程运行,并发现r1=1,那表示Thread1中设置的A的值已经被成功的传递给线程2了。如此,我们可以确保r2=42;在前面的文章中,我已经给出过有关代码控制的类比,来说明#LoadLoad和#StoreStore是如何工作的。不重复了。

用正式术语说就是对Ready的store synchronized-with 对Ready的load。我已经写了一篇有关synchronized-with的文章。现在,可以明确的说,为了让这种技术达到预期效果,aquire和release语义必须施加在同一个变量上——本例中就是Ready——并且load和store都需要是atomic的,这里Ready是一个int,这是PowerPC支持的原生atomic操作。

With Fences in Portable C++11

上面的例子是compiler和processor相关的,支持多平台的一种方法就是使用C++11。
C++11的atomic标准库定义了一个可移植的atomic_thread_fence()函数,使用一个参数表示fence的类型。这里我们感兴趣的是memory_order_acquire和memory_order_release,来替换__lwsync()。

还有一个需要修改的是Ready,虽然在PowerPC上是atomic的,但是其它平台却又不好说了,所以从int改成atomic<int>,实际上现在的主流CPU,对字节对齐的int的load和store都是atomic的了,这里我们只管修改一下。


参数memory_order_relaxed表示:保证操作是原子的,但是不加上任何额外的ordering限制(memory barrier)。

再次,在PowerPC上,atomic_thread_fence()被实现成lwsync,在ARM上是dmb。在x86/64上将被简单的替换成compiler barrier,因为在x86/64上,每一个load都暗含了acquire语义,每一个store都暗含了release语义,这也是为什么x86/64被称为是strong ordered的原因(是的,除了#StoreLoad barrier)。

Without Fences in Portable C++11

在C++11,可以不需要显示的指明fence操作,只需要将memory ordering的限制直接是施加到对Ready的操作之中【更新:这和使用单独的fence并不一样,技术上说,这里没有那么严格的限制】。


为了达到要求的barrier,编译器会生成必要的指令。而别的,在Itanium上,每一个操作都可以简单的实现为:ld.acq和st.rel。和前面一样,r1==1意味着一个synchronized-with关系,保证了r2=42;

这个是使用C++11达到acquire and release语义的推荐方式,实际上atomic_thread_fence()是后面才加的。

Acquire and Release While Locking

你可以看到,上面没有一个例子利用acquire and release语义提供的#LoadStore barrier。实际上,#LoadLoad和#StoreStore已经足够了。
(注:因为上面的例子中,对于flag和data,都是1个线程写,另一个线程读,没有一个线程读写的情况)

#LoadStore必不可缺的一种情况是使用acquire and release语义实现一个mutex。实际上,这也是名字的来源:获取一个lock暗示着acquire语义,释放一个lock意味着release语义!所有之间的内存操作都被认为是在一个barrier三明治中,阻止跨越边界的任何不想要的内存重排序。

这里,acquire and release语义保证所有在hold lock期间的修改都会完全的传播给下一个获得锁的线程。每一种lock的实现,甚至是你自己的实现,都应该提供这种保证。再一次,这是关于在多线程之间传递信息的,特别是multicore或者multiprocessor环境。

在接下来的文章中,我会使用C++11展示一个例子,在真实的硬件上,如果没有acquire和release语义,将会出现错误的结果。
(注:也就是前面翻译的在ARMv7平台上测试的例子)


0 0
原创粉丝点击