在Windows下,有三个编译器内部函数来实现内存屏障:
1. _ReadBarrier;
2. _WriteBarrier;
3. _ReadWriteBarrier;
然而,我发现了一个奇怪的问题:_ReadBarrier似乎是一个虚拟函数什么都不做!以下是我的VC ++ 2012生成的汇编代码。
我的问题是:如何在汇编指令中实现内存屏障功能?
int main()
{
013EEE10 push ebp
013EEE11 mov ebp,esp
013EEE13 sub esp,0CCh
013EEE19 push ebx
013EEE1A push esi
013EEE1B push edi
013EEE1C lea edi,[ebp-0CCh]
013EEE22 mov ecx,33h
013EEE27 mov eax,0CCCCCCCCh
013EEE2C rep stos dword ptr es:[edi]
int n = 0;
013EEE2E mov dword ptr [n],0
n = n + 1;
013EEE35 mov eax,dword ptr [n]
013EEE38 add eax,1
013EEE3B mov dword ptr [n],eax
_ReadBarrier();
n = n + 1;
013EEE3E mov eax,dword ptr [n]
013EEE41 add eax,1
013EEE44 mov dword ptr [n],eax
}
013EEE56 xor eax,eax
013EEE58 pop edi
013EEE59 pop esi
013EEE5A pop ebx
013EEE5B add esp,0CCh
013EEE61 cmp ebp,esp
013EEE63 call __RTC_CheckEsp (013EC3B0h)
013EEE68 mov esp,ebp
013EEE6A pop ebp
013EEE6B ret
答案 0 :(得分:8)
_ReadBarrier
,_WriteBarrier
和_ReadWriteBarrier
为intrinsics that affect how the compiler can reorder code;它们与CPU内存屏障完全无关,仅对特定类型的内存有效(请参阅“受影响的内存”here)。
MemoryBarrier()
是用于强制CPU内存屏障的内在函数。但是,Microsoft的建议是使用std::atomic<T>
继续使用VC ++。
答案 1 :(得分:4)
现代处理器能够在实际“完成”指令之前执行相当长的指令,因此当涉及某些类型的存储器操作时,使用内存屏障来阻止它运行到远处。需要严格的排序 - 对于大多数事情,如果你在变量b之前写入变量a,或者在变量之前写入b,则实际上并不重要。但有时它确实如此。
x86指令集包含lfence
,sfence
和fence
,它们分别是“阻塞”加载,存储和所有内存操作的指令。关于“栅栏”或“屏障”指令的要点是确保屏障指令之前的所有指令在屏障可以继续之后的下一条指令之前完成了它们的加载,存储或两者。
如果您正在实现信号量,互斥量或类似指令,这很重要,因为在继续读取其他数据之前,存储值“我已锁定信号量”非常重要。换句话说,事情可能会出错。
请注意,除非您真的知道自己在处理内存障碍方面做了什么,否则最好不要使用它们 - 并且依赖现有的解决相同问题的代码 - std::atomic
是为这些代码提供资金的地方。我写了很多“棘手的”代码,但只有一两次我的代码中需要一个内存屏障。
有几次,我需要让编译器不要传播代码,你可以用“无操作函数”来做,显然现在甚至还有特殊的内部函数来做。
答案 2 :(得分:0)
有几点需要考虑。也许是
首先,障碍只对多线程产生影响
代码,大多数编译器需要一个特殊的选项来生成
多线程代码。像_ReadBarrier
这样的东西差不多
当然是编译器内置函数,应该什么都不做,除非
你已经给出了多线程代码的选项。
第二个是硬件需要的东西,即使在一个 多线程的上下文,各不相同。在大多数机器上我都是 工作(大约四十年),机器永远不需要 任何事情;只有机器才有障碍 复杂的读写流水线。 (大多数早期的机器 甚至没有围栏或障碍指令,所以产生了 代码必须为空。)