并行编程中的 READ_ONCE 和 WRITE_ONCE
READ_ONCE and WRITE_ONCE in Parallel programming
在《Is Parallel Programming Hard, And, If So, You Can Do About It?》一书中,作者使用了几个我不明白它们实际作用的宏。
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
#define READ_ONCE(x) \
({ typeof(x) ___x = ACCESS_ONCE(x); ___x; })
#define WRITE_ONCE(x, val) \
do { ACCESS_ONCE(x) = (val); } while (0)
我不明白 ACCESS_ONCE
宏的作用以及为什么它需要对 volatile 指针类型的对象进行强制转换和取消引用。
READ_ONCE
宏末尾的__x
有什么用?
下面还有一些我不理解的宏的用法。
Here is a list of situations allowing plain loads and stores for some accesses to a
given variable, while requiring markings (such as READ_ONCE() and WRITE_ONCE())
for other accesses to that same variable:
- A shared variable is only modified by a given owning CPU or thread, but is read
by other CPUs or threads. All stores must use WRITE_ONCE(). The owning CPU
or thread may use plain loads. Everything else must use READ_ONCE() for loads.
- A shared variable is only modified while holding a given lock, but is read by
code not holding that lock. All stores must use WRITE_ONCE(). CPUs or threads
holding the lock may use plain loads. Everything else must use READ_ONCE() for
loads.
- A shared variable is only modified while holding a given lock by a given owning
CPU or thread, but is read by other CPUs or threads or by code not holding that
lock. All stores must use WRITE_ONCE(). The owning CPU or thread may use
plain loads, as may any CPU or thread holding the lock. Everything else must use
READ_ONCE() for loads.
- A shared variable is only accessed by a given CPU or thread and by a signal or
interrupt handler running in that CPU’s or thread’s context. The handler can use
plain loads and stores, as can any code that has prevented the handler from being
invoked, that is, code that has blocked signals and/or interrupts. All other code
must use READ_ONCE() and WRITE_ONCE().
- A shared variable is only accessed by a given CPU or thread and by a signal or
interrupt handler running in that CPU’s or thread’s context, and the handler always
restores the values of any variables that it has written before return. The handler
can use plain loads and stores, as can any code that has prevented the handler from
being invoked, that is, code that has blocked signals and/or interrupts. All other
code can use plain loads, but must use WRITE_ONCE() to prevent store tearing,
store fusing, and invented stores.
首先我们如何使用这些宏同时访问内存?据我所知,volatile 关键字对于并发内存访问是不安全的。
在项目编号 1 中,我们如何使用 READ_ONCE
和 WRITE_ONCE
访问共享变量而不发生数据竞争?
在第 2 项中,为什么他使用 WRITE_ONCE
宏,而只允许通过持有锁进行写入。为什么 read 不需要持有锁?
这些宏是在支持编译器(GCC,也许其他一些)上强制执行某种级别的原子性(但没有同步)的方法。它们在 Linux 中被大量使用,因为它比 C11 早了很多。
在 GCC 语义中,volatile
导致恰好发出一条访问指向值的指令(至少如果该值是字长的)。在 Linux 支持的所有体系结构中,对齐的字大小访问是原子的,因此整个构造导致一个单一的原子访问。
(我指的是machine word ofc,不是某些知名平台上的WORD类型)。
据我所知,这等同于使用带有 memory_order_relaxed 的 C++ 原子(正如评论中指出的那样),除了那些需要 every 访问是原子的虽然这在某些模式中实际上并不需要(例如,如果只有一个线程写入一个变量,它自己的读取不需要是原子的,并且原子读写操作 肯定 不必要在这种情况下)。
在《Is Parallel Programming Hard, And, If So, You Can Do About It?》一书中,作者使用了几个我不明白它们实际作用的宏。
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
#define READ_ONCE(x) \
({ typeof(x) ___x = ACCESS_ONCE(x); ___x; })
#define WRITE_ONCE(x, val) \
do { ACCESS_ONCE(x) = (val); } while (0)
我不明白 ACCESS_ONCE
宏的作用以及为什么它需要对 volatile 指针类型的对象进行强制转换和取消引用。
READ_ONCE
宏末尾的__x
有什么用?
下面还有一些我不理解的宏的用法。
Here is a list of situations allowing plain loads and stores for some accesses to a given variable, while requiring markings (such as READ_ONCE() and WRITE_ONCE()) for other accesses to that same variable:
- A shared variable is only modified by a given owning CPU or thread, but is read by other CPUs or threads. All stores must use WRITE_ONCE(). The owning CPU or thread may use plain loads. Everything else must use READ_ONCE() for loads.
- A shared variable is only modified while holding a given lock, but is read by code not holding that lock. All stores must use WRITE_ONCE(). CPUs or threads holding the lock may use plain loads. Everything else must use READ_ONCE() for loads.
- A shared variable is only modified while holding a given lock by a given owning CPU or thread, but is read by other CPUs or threads or by code not holding that lock. All stores must use WRITE_ONCE(). The owning CPU or thread may use plain loads, as may any CPU or thread holding the lock. Everything else must use READ_ONCE() for loads.
- A shared variable is only accessed by a given CPU or thread and by a signal or interrupt handler running in that CPU’s or thread’s context. The handler can use plain loads and stores, as can any code that has prevented the handler from being invoked, that is, code that has blocked signals and/or interrupts. All other code must use READ_ONCE() and WRITE_ONCE().
- A shared variable is only accessed by a given CPU or thread and by a signal or interrupt handler running in that CPU’s or thread’s context, and the handler always restores the values of any variables that it has written before return. The handler can use plain loads and stores, as can any code that has prevented the handler from being invoked, that is, code that has blocked signals and/or interrupts. All other code can use plain loads, but must use WRITE_ONCE() to prevent store tearing, store fusing, and invented stores.
首先我们如何使用这些宏同时访问内存?据我所知,volatile 关键字对于并发内存访问是不安全的。
在项目编号 1 中,我们如何使用 READ_ONCE
和 WRITE_ONCE
访问共享变量而不发生数据竞争?
在第 2 项中,为什么他使用 WRITE_ONCE
宏,而只允许通过持有锁进行写入。为什么 read 不需要持有锁?
这些宏是在支持编译器(GCC,也许其他一些)上强制执行某种级别的原子性(但没有同步)的方法。它们在 Linux 中被大量使用,因为它比 C11 早了很多。
在 GCC 语义中,volatile
导致恰好发出一条访问指向值的指令(至少如果该值是字长的)。在 Linux 支持的所有体系结构中,对齐的字大小访问是原子的,因此整个构造导致一个单一的原子访问。
(我指的是machine word ofc,不是某些知名平台上的WORD类型)。
据我所知,这等同于使用带有 memory_order_relaxed 的 C++ 原子(正如评论中指出的那样),除了那些需要 every 访问是原子的虽然这在某些模式中实际上并不需要(例如,如果只有一个线程写入一个变量,它自己的读取不需要是原子的,并且原子读写操作 肯定 不必要在这种情况下)。