条件变量通常用于在互斥锁下修改它们引用的状态。但是,当状态只是单个仅设置标志时,不需要互斥体来阻止同时执行。所以人们可能想做这样的事情:
flag = 1;
pthread_cond_broadcast(&cvar);
但是,只有当
pthread_cond_broadcast
意味着写内存屏障时,这才是安全的;否则,等待线程可能会在标志写入之前看到条件变量广播。也就是说,等待线程可能会唤醒,消耗 cvar 信号,但看到标志仍然0
。
所以,我的问题是:
pthread_cond_broadcast
和 pthread_cond_signal
调用是否意味着写内存屏障?如果是这样,相关 POSIX(或其他)规范中在哪里指定了这一点? 规范在这一点上似乎不清楚。
注意:我知道,在实践中,这确实会导致内存屏障(在 Linux 上,因为线程唤醒意味着完整的 CPU 内存屏障,而跨库函数调用意味着编译器内存屏障)。然而,我对规范的保证感兴趣。
无论是否存在内存障碍,代码仍然不正确。考虑阅读方面:
while (flag == 0)
pthread_cond_wait(&cvar, &mutex);
如果读取端在测试
flag == 0
和执行等待之间暂停,写入端可以执行flag = 1; pthread_cond_signal(&cvar);
。然后读取端将完全错过唤醒 - 它将永远等待。请记住,唤醒不会排队 - 如果在发出条件变量信号时没有服务员,则该信号无效。为了避免这种情况,写入端无论如何都需要锁定互斥锁。
在 POSIX 下,如果从一个线程写入变量并从另一个线程读取变量,则必须使用互斥体保护它。
pthread_cond_broadcast
也不例外。
如果您的平台/编译器提供原子变量,那么他们可能会对这些变量做出额外的保证。例如,如果
flag
是 C++11 std::atomic<int>
,那么此代码就可以。
编译器有权假设非易失性对象的值没有被虚假更改。本质上是能够假设即使是最简单的 CSE 优化也是有效的(并且它使得优化无法检测到)。
它是一个基本不变量,也是任何关于可变状态的局部推理的基础。
对在 CPU 级别具有原子加载和存储的偶数类型进行此类修改可能适用于非/有限优化编译,但当允许编译器分析程序以推断内容时,在更高的优化中会失败。
所以:不要这样做。
据我所知,
pthread_cond_signal()
和pthread_cond_broadcast
不需要内存屏障,因为pthread_cond_wait()
是在互斥锁锁定的情况下调用的,编写者在更改变量时还需要锁定互斥体,当编写者释放锁时,有记忆障碍。
pthread_mutex_lock(&lock);
flag = 1;
pthread_mutex_unlock(&lock); // memory barrier here
pthread_cond_broadcast(&cvar);
在
pthread_cond_broadcast()
之前打电话pthread_mutex_unlock()
也对