互斥锁的存在是否有助于摆脱 volatile 关键字?

我有一个多 R/W 锁类,它保持读、写和挂起的读、挂起的写计数器.互斥体保护它们免受多个线程的影响.

I have a multi-R/W lock class that keeps the read, write and pending read , pending write counters. A mutex guards them from multiple threads.

我的问题是我们是否还需要将计数器声明为 volatile,以便编译器在进行优化时不会搞砸.

My question is Do we still need the counters to be declared as volatile so that the compiler won't screw it up while doing the optimization.

或者编译器是否考虑到计数器由互斥锁保护.

Or does the compiler takes into account that the counters are guarded by the mutex.

我了解互斥锁是一种用于同步的运行时机制,而volatile"关键字是编译时指示编译器在进行优化时做正确的事情.

I understand that the mutex is a run time mechanism to for synchronization and "volatile" keyword is a compile time indication to the compiler to do the right thing while doing the optimizations.

问候,-杰.

推荐答案

这里有2个基本不相关的项目,总是混淆.

There are 2 basically unrelated items here, that are always confused.

  • 易变
  • 线程、锁、内存屏障等.

volatile 用于告诉 编译器 生成代码以从内存中读取变量,而不是从寄存器中读取.并且不要重新排序代码.一般来说,不要优化或走捷径".

volatile is used to tell the compiler to produce code to read the variable from memory, not from a register. And to not reorder the code around. In general, not to optimize or take 'short-cuts'.

内存屏障(由互斥体、锁等提供),正如 Herb Sutter 在另一个答案中所引用的那样,用于防止 CPU 重新排序读/写内存请求,无论编译器如何表示去做吧.即不要优化,不要走捷径 - 在 CPU 级别.

memory barriers (supplied by mutexes, locks, etc), as quoted from Herb Sutter in another answer, are for preventing the CPU from reordering read/write memory requests, regardless of how the compiler said to do it. ie don't optimize, don't take short cuts - at the CPU level.

相似,但实际上非常不同.

Similar, but in fact very different things.

在您的情况下,并且在大多数锁定情况下,不需要 volatile 的原因是因为为了锁定而进行了 函数调用.即:

In your case, and in most cases of locking, the reason that volatile is NOT necessary, is because of function calls being made for the sake of locking. ie:

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
}

除非编译器可以检查 library_func() 并确定它没有触及 x,否则它将在返回时重新读取 x.这甚至没有 volatile.

unless the compiler can examine library_func() and determine that it doesn't touch x, it will re-read x on the return. This is even WITHOUT volatile.

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;
}

在为 temp1 读取 obj.x 后,编译器将重新读取 temp2 的 obj.x - 不是因为锁的魔力 - 而是因为不确定 lock() 是否修改了 obj.您可能可以设置编译器标志来积极优化(无别名等),因此不会重新读取 x,但是您的一堆代码可能会开始失败.

After reading obj.x for temp1, the compiler is going to re-read obj.x for temp2 - NOT because of the magic of locks - but because it is unsure whether lock() modified obj. You could probably set compiler flags to aggressively optimize (no-alias, etc) and thus not re-read x, but then a bunch of your code would probably start failing.

对于 temp3,编译器(希望)不会重新读取 obj.x.如果由于某种原因 obj.x 可能在 temp2 和 temp3 之间发生变化,那么您将使用 volatile(并且您的锁定将被破坏/无用).

For temp3, the compiler (hopefully) won't re-read obj.x. If for some reason obj.x could change between temp2 and temp3, then you would use volatile (and your locking would be broken/useless).

最后,如果您的 lock()/unlock() 函数以某种方式被内联,也许编译器可以评估代码并看到 obj.x 没有改变.但我在这里保证两件事之一:- 内联代码最终会调用一些操作系统级别的锁定函数(从而阻止评估)或- 你调用了一些你的编译器会识别的 asm 内存屏障指令(即包装在像 __InterlockedCompareExchange 这样的内联函数中),从而避免重新排序.

Lastly, if your lock()/unlock() functions were somehow inlined, maybe the compiler could evaluate the code and see that obj.x doesn't get changed. But I guarantee one of two things here: - the inline code eventually calls some OS level lock function (thus preventing evaluation) or - you call some asm memory barrier instructions (ie that are wrapped in inline functions like __InterlockedCompareExchange) that your compiler will recognize and thus avoid reordering.

附注我忘了提――对于 pthreads 的东西,一些编译器被标记为POSIX compliant",这意味着它们将识别 pthread_ 函数并且不会围绕它们进行糟糕的优化.即,即使 C++ 标准还没有提到线程,那些编译器会(至少是最低限度地).

P.S. I forgot to mention - for pthreads stuff, some compilers are marked as "POSIX compliant" which means, among other things, that they will recognize the pthread_ functions and not do bad optimizations around them. ie even though the C++ standard doesn't mention threads yet, those compilers do (at least minimally).

你不需要 volatile.

you don't need volatile.

相关文章