关键部分使用的资源是否需要 volatile?

Hao*_*ang 2 c multithreading volatile

我很好奇volatile关键部分使用的资源是否必要。假设我有两个线程在两个 CPU 上执行,并且它们正在竞争共享资源。我知道我需要一种锁定机制来确保只有一个线程正在该共享资源上执行操作。下面是将在这两个线程上执行的伪代码。

take_lock();

// Read shared resource.
read_shared_resouce();

// Write something to shared resource.
write_shared_resource();

release_lock();
Run Code Online (Sandbox Code Playgroud)

我想知道是否需要使该共享资源成为易失性的,以确保当一个线程读取共享资源时,线程不仅会从寄存器中获取值,而且实际上会从该共享资源中读取值。或者也许我应该使用访问器函数通过一些内存屏障操作来使对该共享资源的访问变得易失,而不是使该共享资源变得易失?

Joh*_*ger 6

我很好奇volatile关键部分使用的资源是否必要。假设我有两个线程在两个 CPU 上执行,并且它们正在竞争共享资源。我知道我需要一种锁定机制来确保只有一个线程正在该共享资源上执行操作。

确保一次只有一个线程访问共享资源只是适合此目的的锁定机制的一部分。除此之外,这种机制还将确保线程Ti在释放锁 L 之前共享对象执行的所有写入在所有其他线程T j随后获取锁L后都可见。就程序的 C 语义而言,尽管存在编译器优化、寄存器使用、CPU 指令重新排序或类似问题。

当使用这种锁定机制时,volatile不会为使线程对共享对象的写入彼此可见提供任何额外的好处。当不使用这种锁定机制时,volatile不提供完整的替代品。

C 的内置(自 C11 起)互斥体提供了合适的锁定机制,至少在使用 C 的内置线程时是如此。pthreads 互斥体、Sys V 和 POSIX 信号量以及各种其他环境中可用的类似同步对象也是如此,每个对象都与相应的多线程系统相关。这些语义在类似 C 的多线程实现中非常一致,至少扩展到 Java。当前 (C17) 语言规范的 5.1.2.4 节描述了 C 内置多线程的语义要求。

volatile用于指示可以在程序的 C 语义范围之外访问对象。这可能会碰巧产生与多线程执行交互的属性,其方式被认为是理想的,但这不是volatile. 如果它是,或者如果volatile足以达到这样的目的,那么我们就不需要_Atomic对象和操作。


前面的评论主要集中在语言级别的语义上,这足以回答这个问题。然而,由于问题专门询问从寄存器访问变量的值,我观察到编译器实际上不需要在该区域执行任何特定于多线程的操作,只要获取和释放锁需要调用函数即可。

特别是,如果函数的执行E写入对其他函数或 的其他执行可见的f对象of,则 C 实现必须确保在E计算任何后续函数调用(例如 is需要释放锁)。这是必要的,因为无论任何其他线程如何,写入的值都必须对被调用函数的执行可见。

类似地,如果E在从函数调用返回后使用o的值(例如需要获取锁),那么它必须从内存加载该值,以确保它看到该函数可能执行的任何写入的效果。

在这方面,多线程的唯一特殊之处在于,实现必须确保过程间分析优化或类似的操作不会破坏锁定和解锁函数所需的内存读写。在实践中,这很少需要特别注意。

  • @EricPostpischil,我的意思是,如果一个 C11 线程获取一个 C11 互斥体,通过写入 *w* 修改 *任何* 对象 *o*,然后释放互斥体,然后任何其他 C11 线程随后获取相同的互斥体并随后读取 * o* 将看到 *w* 或后续写入 *o* 的效果。为了保证这一点, *o* 不必是“易失性”,这就是问题所在。类似的保证适用于 pthreads 线程和互斥体,以及 C++ 线程和互斥体。 (3认同)