我最近在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; }
}
使用者线程执行以下操作(c是指向控件对象的非易失性指针):
...
pthread_mutex_lock(c->control_lock)
while (!c->vthis()->there_is_work_todo) {
pthread_cond_wait(&c->wake_cond, &c->control_lock);
}
...
这里的想法是消费者线程会等到有一些工作要做,生产者通过wake_cond变量发出信号。
这里我不明白为什么控制对象是通过指向"这个"的易失性指针来访问的,这是由方法vthis()返回的。那是为什么?
答案 0 :(得分:5)
在多线程代码中使用volatile
通常是可疑的。 volatile
旨在避免优化对内存的读取和写入,这在映射到硬件寄存器的特殊地址上发生此类读取和写入时非常有用。例如,请参阅how volatile
is useless to prevent data-races,以及它如何(ab)用作幻像类型......
由于作者使用了正确的同步(互斥和条件变量),因此使用volatile
是非常可疑的。这种用法通常源于误解,最明显的是由Java等语言传播,他们重复使用具有不同语义的相同关键字。
在C和C ++中,多线程代码应该依赖于内存障碍,例如互斥锁和原子操作引入的内存障碍,这些障碍保证了值在不同的CPU内核和缓存之间正确同步。
答案 1 :(得分:1)
在此代码中使用volatile
会产生未定义的行为或冗余:
如果c
指向的对象不是易失性的,则添加volatile
的访问(包括读取)被静态视为导致副作用(以适应编译器不能静态地找出访问是否真的使用volatile对象),但不需要不惜一切代价执行,因为对非易失性对象的副作用不构成可观察的行为。
如果在上调用vthis
的对象是 volatile,则代码具有未定义的行为,因为它在调用之前通过非易失性类型的左值访问易失性对象以前的行。
由于与现有代码的兼容性,代码可能依赖于不优化易失性访问的实现。