Jay*_*y D 9 c++ compiler-construction multithreading mutex volatile
我有一个多R/W锁类,它保持读,写和待处理读,待处理写计数器.互斥锁可以防御多个线程.
我的问题是我们是否仍然需要将计数器声明为volatile,以便编译器在进行优化时不会搞砸它.
或者编译器是否考虑到计数器由互斥锁保护.
我理解互斥锁是用于同步的运行时机制,"volatile"关键字是编译器在编译时执行正确操作的编译时指示.
问候,-Jay.
ton*_*ony 15
这里有两个基本上不相关的项目,总是混淆不清.
volatile用于告诉编译器生成代码以从内存中读取变量,而不是从寄存器中读取变量.并且不重新排序代码.一般来说,不要优化或采取"捷径".
内存障碍(由互斥锁,锁等提供),如Herb Sutter在另一个答案中引用的那样,是为了防止CPU重新排序读/写内存请求,无论编译器如何表达.即不要优化,不要采取捷径 - 在CPU级别.
类似,但实际上是非常不同的东西.
在你的情况下,并且在大多数情况下锁定,不需要volatile的原因是因为为了锁定而进行的函数调用.即:
external void library_func(); // from some external library
global int x;
int f()
{
x = 2;
library_func();
return x; // x is reloaded because it may have changed
}
Run Code Online (Sandbox Code Playgroud)
除非编译器可以检查library_func()并确定它不接触x,否则它将在返回时重新读取x.这甚至没有波动.
int f(SomeObject & obj)
{
int temp1;
int temp2;
int temp3;
int temp1 = obj.x;
lock(obj.mutex); // really should use RAII
temp2 = obj.x;
temp3 = obj.x;
unlock(obj.mutex);
return temp;
}
Run Code Online (Sandbox Code Playgroud)
在读取temp1的obj.x之后,编译器将重新读取temp2的obj.x - 不是因为锁的魔力 - 而是因为它不确定lock()是否修改了obj.您可以设置编译器标志以积极地优化(无别名等),因此不会重新读取x,但是一堆代码可能会开始失败.
对于temp3,编译器(希望)不会重新读取obj.x. 如果由于某种原因obj.x可以在temp2和temp3之间改变,那么你将使用volatile(并且你的锁定会被破坏/无用).
最后,如果你的lock()/ unlock()函数以某种方式内联,那么编译器可能会评估代码并看到obj.x没有被更改.但我保证这里有两件事情之一: - 内联代码最终会调用一些操作系统级锁定函数(从而阻止评估)或 - 你调用一些asm内存屏障指令(即包含在内联函数中,如__InterlockedCompareExchange),编译器将识别这些指令从而避免重新排序.
编辑:PS我忘了提 - 对于pthreads的东西,一些编译器被标记为"POSIX兼容",这意味着,除其他外,他们将识别pthread_函数,而不是围绕它们进行错误的优化.即使C++标准尚未提及线程,那些编译器(至少是最低限度).
你不需要挥发性的.
Mic*_*urr 13
从Herb Sutter的文章"使用关键部分(最好是锁定)到消除种族"(http://www.ddj.com/cpp/201804238):
因此,要使重新排序转换有效,它必须遵守关键部分的一个关键规则来尊重程序的关键部分:代码不能移出关键部分.(代码移入总是可以的.)我们通过要求任何关键部分的开头和结尾的对称单向栅栏语义来强制执行这条黄金法则,如图1中的箭头所示:
- 输入关键部分是获取操作或隐式获取栅栏:代码永远不会越过栅栏,即从栅栏后的原始位置移动到栅栏之前执行.但是,在源代码顺序中出现在围栏之前的代码可以愉快地向下跨越围栏以便稍后执行.
- 退出临界区是一个释放操作,或一个隐式释放栏:这只是反向要求,代码不能向下跨越围栏,只能向上.它保证看到最终版本写入的任何其他线程也将看到它之前的所有写入.
因此,对于编译器为目标平台生成正确的代码,当进入和退出临界区时(并且术语临界区用于它的一般意义,不一定是Win32意义上的CRITICAL_SECTION
结构保护的东西- 关键部分可以受到其他同步对象的保护)必须遵循正确的获取和释放语义.因此,只要在受保护的关键部分中访问它们,就不必将共享变量标记为volatile.
volatile用于通知优化器始终加载位置的当前值,而不是将其加载到寄存器中并假设它不会更改.当使用双端口存储器位置或可以从线程外部源实时更新的位置时,这是最有价值的.
互斥体是一种运行时操作系统机制,编译器实际上并不了解任何内容 - 因此优化器不会考虑这一点.它会阻止多个线程同时访问计数器,但即使互斥锁生效,这些计数器的值仍然会发生变化.
所以,你要标记变量是不稳定的,因为它们可以被外部修改,而不是因为它们在互斥锁中.
保持它们不稳定.
归档时间: |
|
查看次数: |
2693 次 |
最近记录: |