我和同事为在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()是一个特殊函数,所以我们只是受序列点保护吗?
非常感谢.
更新:好的,我可以看到一个趋势,答案解释了为什么不稳定是坏的.我尊重这些答案,但有关该主题的文章很容易在网上找到.我在网上找不到的,以及我问这个问题的原因,就是我如何保护我没有不稳定.如果上面的代码是正确的,那么缓存问题如何无懈可击?
考虑一个简单的(在我的情况下是全局)变量:
int i;
Run Code Online (Sandbox Code Playgroud)
在某处访问此变量
pthread_mutex_lock(i_mutex);
if(i == other value) {
do_something();
}
pthread_mutex_unlock(i_mutex);
Run Code Online (Sandbox Code Playgroud)
另一个线程i 在保持时更新i_mutex.编译器可以缓存值,i所以我没有得到最近的值?一定i是不稳定的?
这样做的pthread_mutex_lock和调用pthread_mutex_unlock函数调用的存储栅栏/屏障指令?或者像compare_and_swap暗示那样的低级指令是否有内存障碍?
看着经过一大堆 的 其他 问题 和 他们的 答案,我得到的印象是有什么在C“挥发性”关键字表示正好没有广泛的协议。
即使标准本身似乎也不够清晰,以至于每个人都无法理解其含义。
除其他问题外:
总结一下问题,似乎(经过大量阅读)“ volatile”保证了类似的结果:该值将不但从/向寄存器,而且至少向内核的L1缓存中读/写,其顺序与读/写出现在代码中。但这似乎没有用,因为在同一线程内从寄存器中读取/写入寄存器已经足够,而与L1缓存进行协调并不能保证与其他线程进行协调。我无法想象仅与L1缓存进行同步的重要性。
用途1
唯一广泛同意使用volatile的似乎是旧的或嵌入式系统,其中某些内存位置通过硬件映射到I / O功能,例如内存中的某个位(直接在硬件中)控制灯光。 ,或内存中的某个位告诉您键盘键是否按下(因为它是通过硬件直接连接到键的)。
看来,“用1”不移植的代码,其目标包括多核系统发生。
USE 2
与“ use 1”没什么不同,是可由中断处理程序(可以控制灯光或存储来自按键的信息)随时读取或写入的内存。但是为此已经存在一个问题,即取决于系统,中断处理程序可能会在 具有自己的内存缓存的不同内核上运行,并且“ volatile”不能保证所有系统上的缓存一致性。
因此,“使用2”似乎超出了“易失性”所能提供的范围。
用途3
我看到的唯一其他无可争议的用途是防止通过指向编译器未意识到的同一内存的不同变量的不同变量对访问进行错误优化。但这可能只是无可争议的,因为人们没有在谈论它-我只看到其中一个提及。而且我认为C标准已经认识到“不同”的指针(例如指向函数的不同args)可能指向同一项目或附近的项目,并且已经指定编译器必须生成即使在这种情况下也可以工作的代码。但是,我无法在最新的标准(500页!)中快速找到此主题。
那么“使用3”也许根本不存在?
因此,我的问题是:
在多核系统的可移植C代码中,“ volatile”是否可以保证任何东西?
浏览最新标准后,答案似乎至少是非常有限的:
1.该标准针对特定类型“ volatile sig_atomic_t”反复指定特殊处理。但是该标准还说,在多线程程序中使用信号功能会导致不确定的行为。因此,该用例似乎仅限于单线程程序与其信号处理程序之间的通信。
2.该标准还为setjmp / longjmp指定了“ volatile”的明确含义。(在其他问题和答案中给出了重要示例代码)。
因此,更精确的问题变成了:
除了(1)允许单线程程序从其信号处理程序接收信息之外,还是(2)允许setjmp,“ volatile”对于多核系统的便携式C代码是否有任何保证?代码以查看在setjmp和longjmp之间修改的变量?
这仍然是一个是/否问题。
如果为“是”,那么最好显示一个无错误的可移植代码示例,如果省略了“ volatile”,则该示例会出现错误。如果为“ no”,那么我认为对于多核目标,在这两种非常特殊的情况下,编译器可以随意忽略“ volatile”。
我最近在C++多线程代码中遇到了volatile关键字的奇怪用法.为了抽象编程模式,我们假设有一个控制对象可以被一个生产者和几个消费者线程访问:
class control_t {
pthread_mutex_t control_lock;
pthread_cond_t wake_cond;
bool there_is_work_todo;
control_t volatile* vthis() { return this; }
}
Run Code Online (Sandbox Code Playgroud)
消费者线程执行以下操作(c是指向控件对象的非易失性指针):
...
pthread_mutex_lock(c->control_lock)
while (!c->vthis()->there_is_work_todo) {
pthread_cond_wait(&c->wake_cond, &c->control_lock);
}
...
Run Code Online (Sandbox Code Playgroud)
这里的想法是消费者线程将等待,直到有一些工作要做,生产者通过wake_cond变量发出信号.
我在这里不明白的是为什么控制对象是通过指向"this"的易失性指针访问的,该指针由方法vthis()返回.这是为什么?