如果只写一个值,我需要一个原子吗?
Do I need an atomic if a value is only written?
假设我有多个线程访问同一个内存位置。而且,如果有的话,他们都写了相同的值,并且 none 的人读取了它。
之后,所有线程会聚(通过锁),然后我才读取该值。我需要为此使用原子吗?
这是针对 x86_64 系统的。该值为 int32.
根据§5.1.2.4 ¶25 and ¶4 of the ISO C11 standard, two different threads writing to the same memory location using non-atomic operations in an unordered fashion causes undefined behavior。如果所有线程都写入相同的值,ISO C 标准也不例外。
尽管 x86/x64 CPU 的 Intel/AMD 规范保证将 32 位整数写入 4 字节对齐地址是原子的,但这样的操作不是ISO C 标准保证是原子的,除非您使用的是 ISO C 标准保证原子的数据类型(例如 atomic_int_least32_t
)。因此,即使您的线程将 int32_t
类型的值写入 4 字节对齐的地址,根据 ISO C 标准,您的程序仍然会导致未定义的行为。
但是,出于实际目的,可以安全地假设编译器正在生成以原子方式执行操作的汇编指令,前提是满足对齐要求。
即使内存写入未对齐并且 CPU 不会自动执行写入指令,您的程序很可能仍会按预期运行。将一个写操作拆分为两个写操作应该无关紧要,因为所有线程都在写入完全相同的值。
如果您决定不使用原子变量,那么您至少应该将变量声明为volatile
。否则,编译器可能会发出汇编指令,导致变量仅存储在 CPU 寄存器中,因此其他 CPU 可能永远看不到该变量的任何更改。
因此,回答您的问题:可能没有必要将您的变量声明为原子变量。但是,它仍然是强烈推荐的。通常,对多个线程访问的变量的所有操作都应该是原子的或受 mutex 保护。此规则的唯一例外是所有线程都对此变量执行 read-only 操作。
玩弄未定义的行为可能很危险,通常不推荐这样做。特别是,如果编译器检测到导致未定义行为的代码,则允许将该代码视为无法访问并将其优化掉。在某些情况下,某些编译器实际上会这样做。有关详细信息,请参阅 this very interesting post by Microsoft Blogger Raymond Chen。
此外,请注意写入同一位置的多个线程(甚至相同的 cache line) can disrupt the CPU pipeline, because the x86/x64 architecture guarantees strong memory ordering which must be enforced. If the CPU's cache coherency protocol 检测到由于另一个 CPU 写入同一缓存行可能导致内存顺序违规,整个 CPU pipeline may be to be clear. 为此,所有线程写入不同的内存位置(在不同的缓存行中,至少相隔64字节)并在所有线程之后分析写入的数据可能会更高效已同步。
假设我有多个线程访问同一个内存位置。而且,如果有的话,他们都写了相同的值,并且 none 的人读取了它。 之后,所有线程会聚(通过锁),然后我才读取该值。我需要为此使用原子吗? 这是针对 x86_64 系统的。该值为 int32.
根据§5.1.2.4 ¶25 and ¶4 of the ISO C11 standard, two different threads writing to the same memory location using non-atomic operations in an unordered fashion causes undefined behavior。如果所有线程都写入相同的值,ISO C 标准也不例外。
尽管 x86/x64 CPU 的 Intel/AMD 规范保证将 32 位整数写入 4 字节对齐地址是原子的,但这样的操作不是ISO C 标准保证是原子的,除非您使用的是 ISO C 标准保证原子的数据类型(例如 atomic_int_least32_t
)。因此,即使您的线程将 int32_t
类型的值写入 4 字节对齐的地址,根据 ISO C 标准,您的程序仍然会导致未定义的行为。
但是,出于实际目的,可以安全地假设编译器正在生成以原子方式执行操作的汇编指令,前提是满足对齐要求。
即使内存写入未对齐并且 CPU 不会自动执行写入指令,您的程序很可能仍会按预期运行。将一个写操作拆分为两个写操作应该无关紧要,因为所有线程都在写入完全相同的值。
如果您决定不使用原子变量,那么您至少应该将变量声明为volatile
。否则,编译器可能会发出汇编指令,导致变量仅存储在 CPU 寄存器中,因此其他 CPU 可能永远看不到该变量的任何更改。
因此,回答您的问题:可能没有必要将您的变量声明为原子变量。但是,它仍然是强烈推荐的。通常,对多个线程访问的变量的所有操作都应该是原子的或受 mutex 保护。此规则的唯一例外是所有线程都对此变量执行 read-only 操作。
玩弄未定义的行为可能很危险,通常不推荐这样做。特别是,如果编译器检测到导致未定义行为的代码,则允许将该代码视为无法访问并将其优化掉。在某些情况下,某些编译器实际上会这样做。有关详细信息,请参阅 this very interesting post by Microsoft Blogger Raymond Chen。
此外,请注意写入同一位置的多个线程(甚至相同的 cache line) can disrupt the CPU pipeline, because the x86/x64 architecture guarantees strong memory ordering which must be enforced. If the CPU's cache coherency protocol 检测到由于另一个 CPU 写入同一缓存行可能导致内存顺序违规,整个 CPU pipeline may be to be clear. 为此,所有线程写入不同的内存位置(在不同的缓存行中,至少相隔64字节)并在所有线程之后分析写入的数据可能会更高效已同步。