互斥锁功能是否足够而没有易失性?

Dav*_*vid 39 c++ multithreading mutex volatile memory-barriers

我和同事为在x86,x64,Itanium,PowerPC和其他10年历史的服务器CPU上运行的各种平台编写软件.

我们刚刚讨论了pthread_mutex_lock()... pthread_mutex_unlock()等互斥函数本身是否足够,或者受保护变量是否需要是volatile.

int foo::bar()
{
 //...
 //code which may or may not access _protected.
 pthread_mutex_lock(m);
 int ret = _protected;
 pthread_mutex_unlock(m);
 return ret;
}
Run Code Online (Sandbox Code Playgroud)

我担心的是缓存.编译器是否可以在堆栈或寄存器中放置_protected的副本,并在赋值中使用该陈旧值?如果没有,是什么阻止了这种情况发生?这种模式的变化是否易受攻击?

我假设编译器实际上并不理解pthread_mutex_lock()是一个特殊函数,所以我们只是受序列点保护吗?

非常感谢.

更新:好的,我可以看到一个趋势,答案解释了为什么不稳定是坏的.我尊重这些答案,但有关该主题的文章很容易在网上找到.我在网上找不到的,以及我问这个问题的原因,就是我如何保护我没有不稳定.如果上面的代码是正确的,那么缓存问题如何无懈可击?

AJG*_*G85 14

volatile根本不需要最简单的答案来进行多线程处理.

很长的答案是像关键部分这样的序列点是平台相关的,就像你正在使用的任何线程解决方案一样,因此大多数线程安全性也与平台有关.

C++ 0x具有线程和线程安全的概念,但是当前的标准没有,因此volatile有时被误认为是防止多线程编程的操作和内存访问重新排序的东西,当它从未打算并且不能被可靠地使用时那样.

volatile在C++中唯一应该使用的是允许访问内存映射设备,允许在setjmp和之间使用变量longjmp,并允许sig_atomic_t在信号处理程序中使用变量.关键字本身不会使变量成为原子.

在C++ 0x中的好消息我们将拥有STL结构std::atomic,可用于保证原子操作和变量的线程安全结构.在您选择的编译器支持它之前,您可能需要转向boost库或破坏一些汇编代码来创建自己的对象以提供原子变量.

PS很多混乱是由Java和.NET实际上使用关键字volatileC++ 强制执行多线程语义引起的,但是在C不适用的情况下也是如此.

  • 在我读到这篇文章之前,我自己并不理解:http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf (2认同)

nin*_*alj 8

您的线程库应包含互斥锁和解锁时适当的CPU和编译器障碍.对于GCC,memoryasm语句中的clobber充当编译器障碍.

实际上,有两件事可以保护您的代码免受(编译器)缓存的影响:

  • 您正在调用非纯外部函数(pthread_mutex_*()),这意味着编译器不知道该函数不会修改您的全局变量,因此必须重新加载它们.
  • 正如我所说,pthread_mutex_*()包括一个编译器障碍,例如:在glibc/x86上pthread_mutex_lock()最终调用宏lll_lock(),它有一个memoryclobber,迫使编译器重新加载变量.


MSN*_*MSN 8

如果上面的代码是正确的,那么缓存问题如何无懈可击?

直到C++ 0x,它不是.并且它没有在C中指定.所以,它实际上取决于编译器.通常,如果编译器不保证它将遵守涉及多个线程的函数或操作的内存访问的排序约束,那么您将无法使用该编译器编写多线程安全代码.请参见Hans J Boehm的主题无法实现的库.

至于你的编译器应该支持线程安全代码的抽象,关于内存障碍的维基百科条目是一个很好的起点.

(至于为什么人们建议volatile,一些编译器将其volatile视为编译器的内存障碍.它绝对不是标准.)

  • *直到 C++0x,它才不是*这个答案是不正确的。`pthread_mutex_lock()` 和 `pthread_mutex_unlock()` 是[保证正确维护内存访问顺序的 POSIX 函数](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_12): (3认同)
  • (续)“应用程序应确保多个控制线程(线程或进程)对任何内存位置的访问受到限制,以便在另一个控制线程可以修改该内存位置时,任何控制线程都无法读取或修改该内存位置。使用同步线程执行以及相对于其他线程同步内存的函数来限制此类访问。以下函数相对于其他线程同步内存: ... `pthread_mutex_lock()` ... `pthread_mutex_unlock()` ... ” (2认同)