我读到volatile
关键字不适合线程同步,事实上根本不需要它。
虽然我明白使用这个关键字是不够的,但我不明白为什么完全没必要。
例如,假设我们有两个线程,线程A仅从共享变量读取,线程B仅写入共享变量。通过例如适当的同步pthreads互斥量被强制执行。
IIUC,没有volatile关键字,编译器可能会查看线程A的代码并说:“这里的变量似乎没有被修改,但是我们有很多读取;让我们只读一次,缓存值并优化所有后续读取。“它也可以查看线程B的代码并说:”我们在这里有很多写这个变量但没有读取;因此,不需要写入值,因此我们可以优化所有写入。“
两种优化都是不正确的。 volatile会阻止 一个。因此,我可能会得出结论,虽然volatile
不足以同步线程,但仍然需要在线程之间共享任何变量。 (注意:我现在读到,实际上volatile
不需要阻止写入符合;所以我不知道如何防止这种错误的优化)
我明白我错在这里。但为什么呢?
答案 0 :(得分:9)
例如,假设我们有两个线程,线程A仅从共享变量读取,线程B仅写入共享变量。通过例如适当的同步pthreads互斥量被强制执行。
IIUC,没有volatile关键字,编译器可能会查看线程A的代码并说:“这里的变量似乎没有被修改,但是我们有很多读取;让我们只读一次,缓存值并优化所有后续读取。“它也可以查看线程B的代码并说:”我们在这里有很多写这个变量但没有读取;因此,不需要写入值,因此我们可以优化所有写入。“
与大多数线程同步原语一样,pthreads互斥操作具有explicitly defined memory visibility semantics。
平台支持pthreads,或者它不支持pthreads。如果它支持pthread,它支持pthreads互斥。要么那些优化是安全的,要么它们不是。如果他们安全,那就没问题了。如果它们不安全,那么任何制作它们的平台都不支持pthreads互斥。
例如,你说"变量似乎没有在这里修改",但它确实 - 另一个线程可以在那里修改它。除非编译器能够证明其优化不能破坏任何符合要求的程序,否则它无法实现。符合程序可以修改另一个线程中的变量。编译器支持POSIX线程,或者它不支持。
实际上,大多数情况都是在大多数平台上自动发生的。只是阻止编译器知道内部互斥操作的作用。另一个线程可以做的任何事情,互斥操作本身都可以做到。所以编译器必须"同步"无论如何进入和退出这些功能之前的记忆。例如,它不能在调用pthread_mutex_lock
的整个寄存器中保留一个值,因为它知道所有内容,pthread_mutex_lock
访问内存中的该值。或者,如果编译器具有关于互斥函数的特殊知识,那么将包括了解这些调用中其他线程可访问的缓存值的无效性。
需要volatile
的平台几乎无法使用。您需要针对特定情况的每个函数或类的版本,其中对象可能对另一个线程可见或者从另一个线程可见。在许多情况下,您几乎必须使所有内容volatile
而不是在寄存器中缓存值是一种性能不起作用。
正如您可能已多次听到的那样,{C语言中指定的volatile
语义只是不会与线程混用。它不仅不够,还会禁用许多完全安全且几乎必不可少的优化。
答案 1 :(得分:4)
缩短已经给出的答案,您不需要将volatile
用于互斥锁,原因很简单:
std::mutex
),它就知道如何处理优化方面的访问(std::mutex
甚至需要) 答案 2 :(得分:1)
使答案更短,不使用互斥锁或信号量,这是一个错误。一旦线程B释放互斥锁(并且线程A得到它),寄存器中包含来自线程B的共享变量值的任何值都保证被写入高速缓存或内存,以防止线程A运行时的竞争条件。读取此变量。
保证这一点的实现依赖于体系结构/编译器。
答案 3 :(得分:0)
关键字volatile
告诉编译器将变量的任何write 或read 视为“可观察的副作用”。就是这样。当然,可观察到的副作用不能被优化掉,并且必须按照程序指示的顺序出现在外面世界;编译器可能不会相互重新排序可观察的副作用。然而,编译器可以自由地对非可观察对象进行重新排序。因此,volatile
仅适用于访问内存映射硬件,Unix风格的信号处理程序等。对于线程间并发,请使用std::atomic
或更高级别的同步对象,如mutex
,condition_variable
和promise/future
。