Memory Fence

随着如今多核、众核处理器的兴起,多线程之间的同步问题也是逐步被你们所看中。今天我将介绍一下Memory Fence同步机制。缓存

 

Memory Fence在CPU和GPU中都能见到。当前基于Intel Core架构的处理器以及基于ARMv7架构的处理器都有Memory Fence特性。而基于CUDA架构的GPU或是支持OpenCL的GPU也都能支持Memory Fence特性。那么Memory Fence是究竟是啥东东呢?多线程

 

简单地来讲就一句话——串行化加载与存储操做。为何要用Memory Fence?由于现代处理器为了提高执行效率使用无序执行引擎(out-of-order engine)。所以,当你先后两条指令没有关联的话,那么处理器可能会先执行后面一条指令,而后再执行前面一条指令。举个例子:架构

 并发

像上面这个Func()函数,在执行时颇有可能会先执行(*b)--再执行(*a)++。这主要是由处理器来调度的。
固然,这种执行模式对于单线程处理来讲不会有问题,可是对多线程并发执行模型而言就会引起问题。咱们来看下面这个例子:async

函数

咱们看到上面这个例子。一个是produce()函数,一个是consume函数,二者实参都是指向同一个对象。那么对于produce,基于处理器优化原则,颇有可能在把rand()函数返回的结果送到*a中去以前先执行flag = 1;语句。这个时候,原本被阻塞的consume一会儿觉醒,因而乎就把未更新的*a的值给打印出来了。测试

为了不这个可能会发生意料不到的结果出现(其实几率真的很低,呵呵),咱们可使用memory fence来确保在更新flag以前,*a已经被写入:优化

 spa

这样咱们可以确保在标志被更新以前,存储器读写操做所有完成。.net

 

PS:上述代码对于并发执行而言仍然不够好,但这里仅仅用于说明问题,呵呵。详细可参照偶以前的文章——

对多核共享变量的读写操做

上面是一种状况,除此以外,还有一些硬件特性会致使存储器不一致性的。好比,在Intel Core架构中引入了Write-Combined(WC)模式(能够用指令MOVNTI)。这是一种写存储器操做模式。在这种模式下,写数据将不通过Cache而直接写到外部存储器中。因为直接对外部存储器写一个字的数据要花费数十个周期(具体的周期数要看采用哪一种SDRAM,对该RAM的写操做模式以及总线频率和处理器频率),为了提升效率,这里使用了先写缓存,等缓存满了以后再批量写入SDRAM的机制。

这样一来,咱们能够想象,当有多个核对同一地址进行写操做,若是咱们对写顺序的一致性作要求的话,应当使用Memory Fence。不然,最终写的结果将是不可预测的。

 

下面我将借助Intel最新的Intel C++ Composer 2011来为你们演示这个奇迹。

在各位尝试下面代码以前,务必确保编译器的优化选项设为最大,而且是在Release模式下。

 

咱们将会很是惊讶地发现,上述代码执行的结果竟然为:0x00!

然而,当咱们把注释掉的_mm_sfence();打开后,结果就变为了0xEF。

不过这是因为Intel编译器的优化致使。Intel编译器会对MOVNTI这类指令作独特的优化。也就是说,在没有SFENCE的状况下,最后两句被提到_mm_stream_si64(&b, a);上面去执行了⋯⋯

因为MOVNTI这类指令对于单个逻辑处理器而言是不会出现存储器不一致状况的,而在多处理器系统下则会发生。下面将举一个真正硬件上的这种存储弱次序特征:

 

上述汇编文件提供了这次测试所必须的对MOVNTDQ指令的发布。

 

 

上述Objective-C代码使用了Apple提供的很是方便的多处理器并行机制——Grand Central Dispatch,能够参考我关于这篇博文介绍。

这里,分出来的一个Block函数由另外一个核去执行,而后经过两个标志进行同步。当主线程调用stream_test完成写数据和标志清0后,另外一个线程就当即读最高字节数据。

在不使用SFENCE的状况下,结果为0x00;当打开汇编文件中的SFENCE以后,结果为0x89。