"modify"运算符例如+=
、|=
和&=
等是原子操作吗?
我知道++
是原子操作(如果你在两个不同的线程中同时执行x++;
,你最终得到的结果是x
增加了2,与关闭优化的x=x+1
不同)。
我的疑问是variable |= constant
这样的操作是否是线程安全的,或者我必须用互斥锁来保护它们?
(...还是这取决于CPU?如果是这种情况,在ARM上会怎样?)
"modify"运算符例如+=
、|=
和&=
等是原子操作吗?
我知道++
是原子操作(如果你在两个不同的线程中同时执行x++;
,你最终得到的结果是x
增加了2,与关闭优化的x=x+1
不同)。
我的疑问是variable |= constant
这样的操作是否是线程安全的,或者我必须用互斥锁来保护它们?
(...还是这取决于CPU?如果是这种情况,在ARM上会怎样?)
你错了。没有任何保证++是原子的,对于复合赋值运算符也是如此,事实上对于任何C++操作都是如此。
inc [address]
的单核架构中,这绝对是原子操作。 - SF.inc
指令的架构怎么办?总的来说,++
不是 原子操作。您只是找到了一个单一狭窄的特例,在这种情况下它并不是一个问题。 - jalfinc [address]
不是原子操作。 - user541686x++
经常用3个指令实现:将X读入寄存器,增加它,然后将其写回内存。
在这些指令之间,您的线程可能会被抢占。
C/C++中没有任何运算符是保证原子性的。它们在你的平台上可能是原子的,但你不会确定。通常,唯一原子操作是一个测试和设置指令,这通常作为实现信号量的基础,在大多数现代CPU中以某种形式可用。
这是编译器和CPU相关的。一些指令集提供了这些操作的原子指令(用于机器大小的整数)。
但是,并不保证你的编译器会使用这些指令,并且不会以非线程安全的方式优化代码。你需要编写汇编程序或使用特定于编译器的技术(例如intrinsics),以提供原子性(或使用使用这些技术之一的库)。
在ARM架构中: ORR/ADD/AND指令需要两个操作数并将结果放置在寄存器中。任意一个操作数都可以是与结果寄存器相同的寄存器,因此它们可以用作原子 |=、+=、&= 操作。
当然,结果会被放置在寄存器中,而第一个操作数也必须来自寄存器,因此你必须确保寄存器加载是原子的。
在您的编译器/平台上,++可能是原子性的,但在C++规范中并未定义为原子性。
如果您想确保以原子方式修改值,则应使用适当的方法,例如Windows上的Interlocked*。
对于所有其他例程也是如此。如果要进行原子操作,则应使用适当的调用而不是标准调用。
_Atomic int a;
...
a += 3
可以编译成一个(原子)无限循环。感谢标准制定者的礼物,但我真的希望你没有这样做。
1:在某些架构中,仅支持特定访问协议的内存才能进行原子操作。例如,ARMv7、MIPS将该序列转换为:
do {
x = LoadLinked(a) + 3;
} while !StoreConditional(x, &a);
但对于一些内存/缓存类型,LoadLinked/StoreConditional未定义。享受调试吧。
2: 相关的是虚假共享,这是LoadLinked、StoreConditional在缓存行(例如32、64、256字节)而不是子块上操作的产物。因此: _Atomic int a[4]; 可能需要4倍的缓存行大小(因此是1024字节)才能安全地允许同时对a[n]和a[n+1]进行原子操作,因为4个CPU可能会争夺更新a[0..3],但从未成功。
希望下一个标准能认识到属性修饰的固有失败,并恢复c89作为合适的C标准。
它们不仅不是原子操作,就像所有操作一样,还可能产生非常有趣的结果。例如,如果编译器看到它写入了x,它可以将x用作临时变量,而不是使用寄存器或堆栈空间。这意味着x可能暂时包含任何值,而不仅仅是对x有意义的值。
http://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong
你必须保护你的变量,例如使用互斥锁
INC m64
作为RMW操作也不能保证是原子性的,除非使用LOCK
前缀。 - Arne Vogel