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)。因此,当你前后两条指令没有关联的话,那么处理器可能会先执行后面一条指令,然后再执行前面一条指令。举个例子:
void Func(int *a, int *b)
{ (*a)++; (*b)--;
}
像上面这个Func()函数,在执行时很有可能会先执行(*b)--再执行(*a)++。这主要是由处理器来调度的。
当然,这种执行模式对于单线程处理来说不会有问题,但是对多线程并发执行模型而言就会引发问题。我们来看下面这个例子:
volatile int flag = 0; void produce(int *a)
{ *a = (int)rand(); flag = 1;
} void consume(int *a)
{while(flag == 0);printf("The value is: %d/r/n", *a);flag = 0;
}
我们看到上面这个例子。一个是produce()函数,一个是consume函数,两者实参都是指向同一个对象。那么对于produce,基于处理器优化原则,很有可能在把rand()函数返回的结果送到*a中去之前先执行flag = 1;语句。这个时候,本来被阻塞的consume一下子觉醒,于是乎就把未更新的*a的值给打印出来了。
为了避免这个可能会发生意料不到的结果出现(其实概率真的很低,呵呵),我们可以使用memory fence来确保在更新flag之前,*a已经被写入:
void produce(int *a)
{*a = (int)rand();MemoryFence(); // Use memory fenceflag = 1;
}
这样我们能够确保在标志被更新之前,存储器读写操作全部完成。
PS:上述代码对于并发执行而言仍然不够好,但这里仅仅用于说明问题,呵呵。详细可参照偶之前的文章——
对多核共享变量的读写操作
上面是一种情况,除此之外,还有一些硬件特性会导致存储器不一致性的。比如,在Intel Core架构中引入了Write-Combined(WC)模式(可以用指令MOVNTI)。这是一种写存储器操作模式。在这种模式下,写数据将不经过Cache而直接写到外部存储器中。由于直接对外部存储器写一个字的数据要花费数十个周期(具体的周期数要看采用哪种SDRAM,对该RAM的写操作模式以及总线频率和处理器频率),为了提高效率,这里使用了先写缓存,等缓存满了之后再批量写入SDRAM的机制。
这样一来,我们可以想象,当有多个核对同一地址进行写操作,如果我们对写顺序的一致性做要求的话,应当使用Memory Fence。否则,最终写的结果将是不可预测的。
下面我将借助Intel最新的Intel C++ Composer 2011来为大家演示这个奇迹。
在各位尝试下面代码之前,务必确保编译器的优化选项设为最大,并且是在Release模式下。
#include <stdio.h>
#include <emmintrin.h>int main(void)
{long a = 0x0123456789abcdef;long b = 0x0;_mm_stream_si64(&b, a);//_mm_sfence();unsigned char c = *(unsigned char*)&b;printf("The value is: 0x%.2X/n", c);
}
我们将会非常惊讶地发现,上述代码执行的结果居然为:0x00!
然而,当我们把注释掉的_mm_sfence();打开后,结果就变为了0xEF。
不过这是由于Intel编译器的优化导致。Intel编译器会对MOVNTI这类指令做独特的优化。也就是说,在没有SFENCE的情况下,最后两句被提到_mm_stream_si64(&b, a);上面去执行了⋯⋯
由于MOVNTI这类指令对于单个逻辑处理器而言是不会出现存储器不一致情况的,而在多处理器系统下则会发生。下面将举一个真正硬件上的这种存储弱次序特征:
// stream_test.s
_stream_test:mov %rdi, %r9 // deliver the buffer addressmov $0x0123456789abcdef, %r8movd %r8, %xmm0pshufd $0, %xmm0, %xmm0movntdq %xmm0, (%r9)//sfencemovq $0, (%rsi) // clear the flagret
上述汇编文件提供了此次测试所必须的对MOVNTDQ指令的发布。
// main.m
#import <Foundation/Foundation.h>static volatile unsigned char __attribute__((aligned(16))) buffer[16];
static volatile BOOL flagStart = NO;
static volatile BOOL flagEnd = NO;extern void stream_test(volatile void*, volatile BOOL*);int main(int argc, const char * argv[])
{NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // insert code here...dispatch_queue_t queue = dispatch_queue_create("zenny_chen_firstQueue", NULL);dispatch_async(queue, ^(void) {for(int i = 0; i < 16; i++) buffer[i] = 0;flagStart = YES;while(flagStart);unsigned char c = buffer[15];printf("The value is: 0x%.2X/n", c);flagEnd = YES;});while(!flagStart);stream_test(buffer, &flagStart); while(!flagEnd);[pool drain];return 0;
}
上述Objective-C代码使用了Apple提供的非常方便的多处理器并行机制——Grand Central Dispatch,可以参考我关于这篇博文介绍。
这里,分出来的一个Block函数由另一个核去执行,然后通过两个标志进行同步。当主线程调用stream_test完成写数据和标志清0后,另一个线程就立即读最高字节数据。
在不使用SFENCE的情况下,结果为0x00;当打开汇编文件中的SFENCE之后,结果为0x89。
Memory Fence相关推荐
- 多核心CPU并行编程中为什么要使用内存屏障 memory barriers / 内存栅栏 memory fence
文章目录 前言 现代Intel® CPU架构 指令集 CISC, RICS ... Intel各个时期的CPU微架构(microarchitecture)特点 P6 Family Microarchi ...
- cuda Memory Fence Functions
thread对内存读写的安全 void __threadfence_block(); void __threadfence(); void __threadfence_system(); 参考: ht ...
- 深入理解Memory Order
深入理解Memory Order cpu 保证 cache 编程技术 lock-free wait-free Read–modify–write Compare-And-Swap(CAS) cas原理 ...
- 理解 C++ 的 Memory Order 以及 atomic 与并发程序的关系
为什么需要 Memory Order 如果不使用任何同步机制(例如 mutex 或 atomic),在多线程中读写同一个变量,那么,程序的结果是难以预料的.简单来说,编译器以及 CPU 的一些行为,会 ...
- CUDA ---- Shared Memory
CUDA SHARED MEMORY shared memory在之前的博文有些介绍,这部分会专门讲解其内容.在global Memory部分,数据对齐和连续是很重要的话题,当使用L1的时候,对齐问题 ...
- Why Memory Barriers中文翻译(下)
转载自:Why Memory Barriers中文翻译(下) 在上一篇why memory barriers文档中,由于各种原因,有几个章节没有翻译.其实所谓的各种原因总结出一句话就是还没有明白那些章 ...
- WORT: Write Optimal Radix Tree for Persistent Memory Storage Systems
WORT: Write Optimal Radix Tree for Persistent Memory Storage Systems FAST17的一篇文章,介绍了内存索引中使用基数树保证数据一致 ...
- Shared Memory
1.引言 在global memory部分,数据对齐和连续是提升性能的很重要的因素,当使用L1 cache的时候,对齐问题不再是问题,但是非连续的获取内存依然会降低性能.依赖于算法本质,某些情况下,非 ...
- 内存模型-Memory Model
文章目录 1.1 CPU Cache 的产生背景 1.2 CPU Cache 模型 1.3 什么是 Cache Line 1.4 Flase Sharing 问题 1.5 CPU 缓存一致性协议 1. ...
最新文章
- 【组队学习】【35期】SQL编程语言
- asp+Access程序在Windows Server 2003 Enterprise Edition服务器上运行时无法显示备注字段的解决方法...
- pr防抖插件_FCPX/AE/Pr视频稳定防抖动插件Lock and Load X v2.0版
- SQL进阶随笔--case用法(一)
- Google研究员Ilya Sutskever:成功训练LDNN的13点建议
- ElementUI中对el-table的某一列的时间进行格式化
- C++ : KMP 字符串匹配算法
- “软件宝宝”出生前,安全系列文章(一)
- matlab d=sqrt((i-m)^2+(j-n)^2);,硕士研究生《数字图像处理》作业
- centos mysql mongodb_MySQL与MongoDB
- 系统动力学Vensim的使用
- matlab光滑曲线链接,在Matlab中使用光滑曲线连接点
- windows 无法完成格式化_U盘无法格式化
- 我对delphi调用第三方SDK的理解
- xp电脑自动锁定计算机,WinXP系统如何设置电脑自动关机?
- Office2021官方镜像
- Quartus生成原理图
- Matlab中在一个矩阵后面加apos是什么意思?绘图时出现错误该如何修改,麻烦解答一下,谢谢了
- 既生AtomicXXX,何生LongAdder?
- 傻瓜式操作实现华为手机与其他品牌电脑实现NFC一碰传
热门文章
- Android主题更换换肤
- 关于读书的,读书笔记的一点总结
- 【QML】实现一个炫酷小键盘
- 千峰JAVA逆战班Day37
- 2021,“韭零后”的智商税涌向了哪里?
- Java面向对象之泛型基本使用
- PSINS工具箱学习(一)下载安装初始化、SINS-GPS组合导航仿真、习惯约定与常用变量符号、数据导入转换、绘图显示
- mysql ratio_to_report_ratio_to_report 分析函数求占比
- millenium panel汉化版安装教程
- 麦芒6刷Android10,麦芒6刷机包 EMUI5.1 186精简优化 高级设置 稳定实用-刷机之家