我正在使用pthread多线程库在Linux上编写一些代码,现在我想知道当使用-Ofast -lto -pthread
编译时,下面的代码是否安全。
// shared global
long shared_event_count = 0;
// ...
pthread_mutex_lock(mutex);
while (shared_event_count <= *last_seen_event_count)
pthread_cond_wait(cond, mutex);
*last_seen_event_count = shared_event_count;
pthread_mutex_unlock(mutex);
调用
pthread_*
函数是否足够,还是应该包含内存屏障以确保在循环期间全局变量shared_event_count
的更改实际上已被更新?如果没有内存屏障,编译器可能自由地将变量优化为寄存器整数,对吗?当然,我可以将共享整数声明为volatile
,这将防止在循环期间仅将变量内容保留在寄存器中,但如果我在循环中多次使用该变量,则仅检查循环条件的新状态可能更有意义,因为这可以允许更多的编译器优化。经过测试,以上代码似乎正常工作并且另一个线程所做的更改也能被生成的代码看到。然而,是否有任何规范或文档实际上保证了这一点?
通常的解决方案似乎是“不要过度优化多线程代码”,但这似乎是一个贫民的解决方法,而不是真正解决问题。我宁愿编写正确的代码,并让编译器在规范范围内尽可能多地进行优化(任何被优化破坏的代码实际上都是在使用例如C标准的未定义行为等假定稳定行为,除了一些罕见的情况外,其中编译器实际上输出无效代码,但这在今天似乎非常非常罕见)。
我更喜欢编写适用于任何优化编译器的代码-因此,它应该仅使用C标准和pthread库文档中指定的功能。
我在https://www.alibabacloud.com/blog/597460找到了一篇有趣的文章,其中包含如下技巧:
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
这实际上是在Linux内核中首次使用,它触发了旧版GCC编译器的一个错误: https://lwn.net/Articles/624126/
因此,让我们假设编译器实际上遵循规范,并且不包含错误,但实现了规范允许的所有可能的优化。在此假设下,上述代码是否安全?
pthread_mutex_lock()
是否根据规范包括内存屏障,或者编译器是否可以重新排列其周围的语句?
pthread_mutex_lock
将会与其他线程同步内存”是否符合“按照规范”的要求? - pilcrowvolatile
实现为内存屏障,但这不是C标准所要求的。 - Mikko Rantalainenvolatile
不是解决方案,但我们已经确定没有问题。 - ikegami