X86 将uint32写入uint64不是原子的。为什么?

X86 将uint32写入uint64不是原子的。为什么?,x86,atomic,X86,Atomic,在第410页中,它写道: 快速问答5.17: 为什么清单5.4中的inc\u count()不需要使用原子指令 回答: (……)在以下情况下需要原子指令: 每线程计数器变量小于全局计数 (……) 简化后,该句适用于以下示例: uint64 global_count = 0; void f(){ uint32 sum = sum_of_smaller_thread_locals(); # sum is a variable WRITE_ONCE(global_count, su

在第410页中,它写道:

快速问答5.17:

为什么清单5.4中的
inc\u count()
不需要使用原子指令

回答:
(……)在以下情况下需要原子指令: 每线程计数器变量小于全局计数 (……)

简化后,该句适用于以下示例:

uint64 global_count = 0;

void f(){
    uint32 sum = sum_of_smaller_thread_locals(); # sum is a variable 
    WRITE_ONCE(global_count, sum);
}

我无法理解为什么在这种情况下我们需要原子指令?

正如Peter Cordes指出的那样,每个线程的增量都需要原子指令。文中给出了原因,但多余的“然而”略微模糊了它:

这就是说,在发生以下情况时需要原子指令: 每线程计数器变量小于全局变量_ 计数但是,请注意,在32位系统上,每线程计数器 变量可能需要限制为32位才能求和 准确,但使用64位全局_count变量以避免溢出。 在这种情况下,必须将每线程计数器变量归零 定期检查以避免溢出。这一点非常重要 请注意,此归零不能延迟太长时间,也不能导致数据溢出 每个线程的变量将更小。因此,这种做法 对底层系统施加实时要求,反过来 必须极其小心地使用

相反,如果所有变量都是 同样大小,任何变量的溢出都是无害的,因为 总和将是字大小的模


如果主线程清除每线程计数器,则需要通过原子交换来执行此操作,以避免可能的数据丢失。如果每个线程的增量进行清除,为了避免数据丢失,它们将需要一些其他(可能更复杂)类型的联锁。

他们在
inc\u count
中讨论增量,而不是使用原子操作写入
全局\u count
。只有当您处于32位模式并且(在x86上)无法使用MMX/XMM/x87寄存器对
movq
fistp
执行64位原子存储时,才需要原子操作(内核代码就是这样)。只有当你关心编写
global\u count
的原子性时。然后
锁定cmpxchg8b
可以执行64位存储。但在某些(微型)体系结构上,64位原子存储不能以无锁方式完成,因此我认为这不是他们所说的。写入
global\u count
的uint64来自何处并不重要,不管它是来自
uint32\u t
的零扩展还是其他什么。因此,您真正的问题是:为什么写入
uint64\t
需要原子指令,而答案是“除了在32位平台上,它不需要原子指令”。(因为他们谈论的是编译器生成的代码,你可能不能指望编译器在ARM上使用
strd
,例如,即使在ARM uarches上,它是原子的。)也就是说,我不理解他们的观点。如果每线程计数器仍然是普通的
长的
,则使用
写入一次(*p_计数器,*p_计数器+1)滥用非
原子变量
而不是放松的原子加载/增量/放松的原子存储实际上在普通CPU上仍然可以工作,即使它有数据争用UB(从写入开始,
final()
线程也使用
volatile unsigned long*
读取)
volatile
在普通ISO C中并没有避免数据争用UB,虽然在可以编译Linux的编译器中,它是安全的,并且被内核与内联asm一起使用?但是用这个词来形容它是非常奇怪的,因为每线程计数器变小了,而全局计数器变宽了。因为我认为多个线程应该读取
global\u count
。啊,是的,我知道他们现在得到了什么。不只是读取计数器,而是以原子方式将零和和和交换到
global\u count
,因此在任何一点上,总和都是
global\u count
加上每线程计数器中的值。为了不让这个存储被占用(因为现在有两个写入程序),每个线程的增量也需要使用原子RMW。(
counter.fetch\u add(1)
,即x86
lock add dword[fs:per\u thread\u count],1
)如果您只是使用纯存储进行调零,同样的情况也适用:增量仍然必须是原子的,以便在加载和存储之间不能发生存储。该测验答案在解释算法的根本性变化方面做得很差。对我来说,像他们说的那样把计数器归零,而不是让它们自己归零是没有意义的。