c++ - 互斥体的存在是否有助于摆脱 volatile 关键字?

标签 c++ multithreading compiler-construction mutex volatile

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

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

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

我知道互斥锁是一种用于同步的运行时机制,“volatile”关键字是编译时指示编译器在进行优化时做正确的事情。

问候, -周杰伦。

最佳答案

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

  • 不稳定
  • 线程、锁、内存屏障等

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
}

除非编译器可以检查 library_func() 并确定它没有触及 x,否则它将在返回时重新读取 x。这甚至没有 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,但是您的一堆代码可能会开始失败。

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

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

编辑:我忘了提及 - 对于 pthreads 的东西,一些编译器被标记为“POSIX 兼容”,这意味着,除其他外,它们将识别 pthread_ 函数并且不会围绕它们进行糟糕的优化。也就是说,即使 C++ 标准还没有提到线程,那些编译器却提到了(至少是最低限度)。

那么,简短的回答

你不需要 volatile。

关于c++ - 互斥体的存在是否有助于摆脱 volatile 关键字?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1616093/

相关文章:

c++ - Python 提示 SWIG 模块不存在

c++ - 在 C++ 中等待子进程与另一个子进程时遇到问题

c++ - 线程相关问题及调试

java - Java编译器中 "let expression"(LetExpr)的用途?

C# 编译器与委托(delegate)构造函数的奇怪之处

C++ OBJ Loader Fstream 问题

c++ - 构造函数和析构函数调用的顺序?

C# WPF 应用程序随时间卡住

multithreading - 如何在self中使用方法使用函数进行线程化?

objective-c - Objective-C 是如何编译的?