我听说i ++不是一个线程安全的语句,因为在汇编时它减少了将原始值存储为某个地方的temp,递增它,然后替换它,这可能被上下文切换中断.
但是,我想知道++ i.据我所知,这将减少为单个汇编指令,例如'add r1,r1,1',因为它只有一条指令,所以它不会被上下文切换中断.
任何人都可以澄清吗?我假设正在使用x86平台.
你听错了."i++"
对于特定的编译器和特定的处理器体系结构来说,它可能是线程安全的,但它根本没有在标准中强制要求.实际上,由于多线程不是ISO C或C++标准(a)的一部分,因此根据您认为可以编译的内容,您不能认为任何东西都是线程安全的.
++i
可以编译成任意序列是非常可行的,例如:
load r0,[i] ; load memory into reg 0 incr r0 ; increment reg 0 stor [i],r0 ; store reg 0 back to memory
在没有内存增量指令的我(虚构)CPU上,它不是线程安全的.或者它可能很聪明并将其编译成:
lock ; disable task switching (interrupts) load r0,[i] ; load memory into reg 0 incr r0 ; increment reg 0 stor [i],r0 ; store reg 0 back to memory unlock ; enable task switching (interrupts)
其中lock
禁用和unlock
使能中断.但是,即使这样,在一个具有多个CPU共享内存的架构中,这可能不是线程安全的(lock
可能只会禁用一个CPU的中断).
语言本身(或者它的库,如果它没有内置到语言中)将提供线程安全的结构,你应该使用它们而不是依赖于你对将生成什么机器代码的理解(或可能是误解).
像Java的东西synchronized
和pthread_mutex_lock()
(可下一些操作系统的C/C++),你需要寻找到什么(一).
(a)在C11和C++ 11标准完成之前询问了这个问题.这些迭代现在已经在语言规范中引入了线程支持,包括原子数据类型(尽管它们和一般的线程是可选的,至少在C中是可选的).
你不能对++ i或i ++做一个全面的陈述.为什么?考虑在32位系统上递增64位整数.除非底层机器具有四字"加载,递增,存储"指令,否则递增该值将需要多个指令,其中任何指令都可以被线程上下文切换中断.
此外,++i
并不总是"添加一个值".在像C这样的语言中,递增指针实际上会增加指向的东西的大小.也就是说,如果i
是指向32字节结构的指针,则++i
添加32个字节.尽管几乎所有平台都具有原子的"内存地址增量值"指令,但并非所有平台都具有原子"在内存地址处添加任意值"指令.
它们都是线程不安全的.
CPU无法直接使用内存进行数学运算.它通过从内存加载值并使用CPU寄存器进行数学运算来间接完成.
我++
register int a1, a2; a1 = *(&i) ; // One cpu instruction: LOAD from memory location identified by i; a2 = a1; a1 += 1; *(&i) = a1; return a2; // 4 cpu instructions
++我
register int a1; a1 = *(&i) ; a1 += 1; *(&i) = a1; return a1; // 3 cpu instructions
对于这两种情况,存在导致不可预测的i值的竞争条件.
例如,假设有两个并发的++ i线程,每个线程分别使用寄存器a1,b1.并且,执行上下文切换,如下所示:
register int a1, b1; a1 = *(&i); a1 += 1; b1 = *(&i); b1 += 1; *(&i) = a1; *(&i) = b1;
结果,我没有成为i + 2,它变成i + 1,这是不正确的.
为了解决这个问题,在禁用上下文切换的间隔期间,模式CPU提供某种LOCK,UNLOCK cpu指令.
在Win32上,使用InterlockedIncrement()为线程安全做i ++.它比依赖互斥锁要快得多.
如果您在多核环境中跨线程共享一个int,则需要适当的内存屏障.这可能意味着使用互锁指令(例如,请参阅win32中的InterlockedIncrement),或者使用可以提供某些线程安全保证的语言(或编译器).使用CPU级别指令重新排序和缓存以及其他问题,除非您有这些保证,否则不要假设跨线程共享的任何内容都是安全的.
编辑:对于大多数体系结构,您可以假设的一件事是,如果您正在处理正确对齐的单个单词,您将不会得到一个单词,其中包含两个被混合在一起的值的组合.如果两个写入发生在彼此之上,则一个将获胜,另一个将被丢弃.如果你小心,你可以利用这一点,并看到++ i或i ++在单一编写器/多读卡器情况下是线程安全的.
如果你想在C++中使用原子增量,你可以使用C++ 0x库(std::atomic
数据类型)或类似TBB的东西.
曾经有一段时间,GNU编码指南说更新适合一个单词的数据类型"通常是安全的",但是对于SMP机器来说建议是错误的,对于某些体系结构来说是错误的,而在使用优化编译器时则是错误的.
澄清"更新单字数据类型"的评论:
SMP计算机上的两个CPU可以在同一周期中写入相同的内存位置,然后尝试将更改传播到其他CPU和缓存.即使只写入一个数据字,因此写入只需要一个周期完成,它们也会同时发生,因此您无法保证哪个写入成功.您不会获得部分更新的数据,但一次写入将消失,因为没有其他方法可以处理此情况.
比较并交换多个CPU之间的正确坐标,但没有理由相信单字数据类型的每个变量赋值都将使用比较和交换.
虽然一个优化编译器不会影响如何加载/存储编译,它可以改变时,加载/存储发生,如果你希望你的读取和写入在它们出现在源代码相同的顺序发生(,造成了严重的麻烦最着名的是双重检查锁定在vanilla C++中不起作用).
注意 我的原始答案还说英特尔64位架构在处理64位数据时被打破.这不是真的,所以我编辑了答案,但我的编辑声称PowerPC芯片坏了. 将立即值(即常量)读入寄存器时也是如此(参见清单2和清单4中名为"加载指针"的两个部分).但是有一个指令用于在一个周期(lmw
)中从内存加载数据,所以我删除了我的答案部分.