具体来说,如果一个线程正在设置foo,而另一个线程正在设置它(使用glib g_atomic_ *函数),我可以假设读者将看到编写器对变量所做的更新。或者作者是否可以简单地更新寄存器中的值?作为参考,我的目标平台是多核多CPU X86_64机器上的gcc(4.1.2)。
答案 0 :(得分:3)
大多数架构确保(包含)是适当大小和对齐读/写的读写的原子性和一致性(因此每个处理器都会看到给定内存地址(*)的相同主值序列的子序列),并且int
最有可能是合适的大小,编译器通常会确保它们也正确对齐。
但是编译器很少确保如果没有以某种方式标记,他们就不会优化某些读取或写入。我试过编译:
int f(int* ptr)
{
int i, r=0;
*ptr = 5;
for (i = 0; i < 100; ++i) {
r += i*i;
}
*ptr = r;
return *ptr;
}
使用gcc 4.1.2 gcc优化了没有问题第一次写入*ptr
,你可能不想要原子写入。
(*)不要将一致性与一致性混淆:不同地址的读写之间的关系通常会放松,直观但实现成本高,顺序一致。这就是为什么需要内存障碍的原因。
答案 1 :(得分:1)
Volatile只会确保编译器不使用寄存器来保存变量。易失性不会阻止编译器重新排序代码;虽然,它可能暗示不重新排序。
根据架构,某些指令是原子的。写入整数并从整数读取通常是原子的。如果gcc使用原子指令读取/写入整数存储器位置,则如果另一个线程处于写入过程中,则一个线程不会读取“中间垃圾”。
但是,由于编译器重新排序和指令重新排序,您可能会遇到问题。
启用优化后,gcc重新排序代码。当涉及全局变量或函数调用时,Gcc通常不会重新排序代码,因为gcc不能保证相同的结果。易失性可能是gcc重新排序的暗示,但我不知道。如果您遇到重新排序问题,这将作为gcc的通用compiler barrier:
__asm__ __volatile__ ("" ::: "memory");
即使编译器没有重新排序代码,CPU也会在执行期间不断重新排序指令。关于这个问题,这是一个非常好的article。 “内存屏障”用于防止cpu在屏障上重新排序指令。以下是使用gcc制作内存屏障的一种可能方法:
__sync_synchronize();
您还可以执行asm指令来执行不同类型的障碍。
也就是说,我们在不使用来自多个线程的原子操作或互斥锁的情况下读取和写入全局整数,并且没有任何问题。这很可能是因为A)我们在英特尔运行并且英特尔没有积极地重新排序指令,并且B)在我们通过早期读取标志做“坏”之前有足够的代码执行。同样对我们有利的是,许多系统调用都存在障碍,gcc atomic operations是障碍。我们使用了大量的原子操作。
在类似问题的堆栈溢出中,这是一个很好的discussion。