我听说我++不是线程安全的,是++我是线程安全的?

我听说i ++不是一个线程安全的语句,因为在汇编时,它减less了将原始值存储为某个临时值,然后将其replace,然后将其replace,这可能会被上下文切换中断。

但是,我想知道关于++我。 据我所知,这将减less到一个单一的汇编指令,如“add r1,r1,1”,因为它只是一个指令,它将不会被上下文切换中断。

任何人都可以澄清? 我假设正在使用一个x86平台。

你听错了。 很可能"i++"对于特定的编译器和特定的处理器体系结构来说是线程安全的,但它并不是标准中的任务。 实际上,由于multithreading不是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的中断)。

语言本身(或者它的库,如果它没有被构build到语言中)将提供线程安全的构造,你应该使用这些构造,而不是依赖于你理解(或者可能误解)生成的机器代码。

像Java synchronizedpthread_mutex_lock() (在某些操作系统下可用于C / C ++)是您需要查看的(a)


(a)在C11和C ++ 11标准完成之前,这个问题被问到。 这些迭代现在已经在语言规范中引入了线程支持,包括primefaces数据types(尽pipe它们和线程一般都是可选的,至less在C中)。

你不能关于++ i或i ++做一个全面的陈述。 为什么? 考虑在32位系统上增加一个64位整数。 除非底层机器有一个四字“load,increment,store”指令,递增该值需要多条指令,其中任何一条指令都可以被一个线程上下文切换中断。

另外, ++i并不总是“增加一个值”。 在像C这样的语言中,增加一个指针实际上会增加指向的东西的大小。 也就是说,如果i是一个32字节结构的指针, ++i增加了32个字节。 而几乎所有的平台都有一个“内存地址递增值”的指令,这个指令是primefaces的,不是所有的平台都有一个primefaces“在内存地址上增加任意值”指令。

他们都是线程不安全的。

一个CPU不能直接与内存进行math运算。 它通过加载内存中的值并与CPU寄存器进行math运算间接完成。

我++

 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,这是不正确的。

为了解决这个问题,moden CPU在禁止上下文切换的时间间隔内提供某种LOCK,UNLOCK cpu指令。

在Win32上,使用InterlockedIncrement()来执行i ++以实现线程安全。 这比依靠互斥锁要快得多。

如果您在多核环境中跨线程共享int,则需要适当的内存屏障。 这可能意味着使用互锁的指令(例如,参见win32中的InterlockedIncrement),或者使用某种语言(或编译器)来提供某些线程安全的保证。 随着CPU级别的指令重新sorting和caching和其他问题,除非你有这些保证,不要假设跨线程共享是安全的。

编辑:大多数体系结构可以假定的一件事是,如果你正在处理正确alignment的单个单词,你将不会结束一个单词包含两个值混合在一起的组合。 如果两个写法发生在另一个之上,一个会赢,另一个会被丢弃。 如果你小心,你可以利用这一点,并看到++ i或i ++在单个作家/多个阅读器的情况下是线程安全的。

如果你想在C ++中使用一个primefaces增量,你可以使用C ++的0x库( std::atomic datatype)或类似TBB的东西。

曾经有一段时间,GNU编码准则表示,更新符合一个单词的数据types是“通常是安全的”,但SMP机器的build议是错误的, 某些体系结构错误,使用优化编译器时错误。


澄清“更新单字数据types”注释:

SMP机器上的两个CPU有可能在同一个周期内写入相同的内存位置,然后尝试将更改传播到其他CPU和caching。 即使只有一个字的数据正在被写入,所以写入只需要一个周期来完成,它们也同时发生,所以你不能保证哪个写入成功。 你不会得到部分更新的数据,但是一个写就会消失,因为没有其他办法来处理这种情况。

比较和交换在多个CPU之间正确地协调,但没有理由相信每个单字数据types的variables分配将使用比较和交换。

虽然优化编译器不会影响编译加载/存储的方式,但在加载/存储发生它可能会发生变化,如果您希望读取和写入按照它们在源代码中显示的顺序发生,会造成严重的麻烦最有名的是双重locking在香草C + +不起作用)。

注意我的原始答案还表示,英特尔64位架构在处理64位数据时被破坏了。 这是不正确的,所以我编辑了答案,但我的编辑声称PowerPC芯片坏了。 将即时值(即常量)读入寄存器 (请参阅清单2和清单4下的两个名为“加载指针”的部分)时,情况就是如此 。 但是在一个周期( lmw )中有一条从内存中加载数据的指令,所以我删除了我的答案的一部分。

在C / C ++的x86 / Windows上,你不应该认为它是线程安全的。 如果您需要primefaces操作,则应该使用InterlockedIncrement()和InterlockedDecrement() 。

如果你的编程语言没有提到线程,而是在multithreading平台上运行,那么任何语言结构怎么可能是线程安全的呢?

正如其他人指出的那样:您需要通过平台特定的调用来保护对variables的任何multithreading访问。

有一些库抽象出了平台的特殊性,即将到来的C ++标准已经调整了它的内存模型来处理线程(从而可以保证线程安全)。

即使减less到单个汇编指令,直接在内存中增加值,它仍然不是线程安全的。

当在内存中增加一个值时,硬件执行“读 – 修改 – 写”操作:从内存中读取值,递增并写回内存。 x86硬件无法直接在内存上递增; RAM(和caching)只能读取和存储值,而不能修改它们。

现在假设你有两个独立的内核,不pipe是在不同的套接字上,还是共享一个套接字(有或没有共享caching)。 第一个处理器读取该值,在写回更新的值之前,第二个处理器读取该值。 在两个处理器写回数值后,它只会增加一次,而不是两次。

有一种方法可以避免这个问题。 x86处理器(以及大多数多核处理器)能够检测到硬件中的这种冲突,并对其进行sorting,以使整个读取 – 修改 – 写入序列看起来是primefaces的。 然而,由于这是非常昂贵的,只能在代码请求时完成,通常在x86上通过LOCK前缀。 其他体系结构可以通过其他方式来实现,具有相似的结果; 例如,负载链接/存储条件和primefaces比较和交换(最近的x86处理器也有最后一个)。

请注意,使用volatile在这里没有帮助; 它只告诉编译器该variables可能在外部被修改,并且读取该variables不能被caching在寄存器中或被优化。 它不会使编译器使用primefaces基元。

最好的方法是使用primefaces基元(如果您的编译器或库具有它们),或者直接在汇编中使用primefaces指令(使用正确的primefaces指令)。

永远不要认为增量会编译成primefaces操作。 使用InterlockedIncrement或目标平台上存在的任何类似的函数。

编辑:我只是看了这个具体的问题和X86增量是primefaces的单处理器系统,但不是在多处理器系统。 使用锁前缀可以使它成为primefaces,但是使用InterlockedIncrement更便于携带。

1998年的C ++标准对于线程没有什么可说的,尽pipe下一个标准(由于今年或下一个标准)会这样做。 因此,在没有提及实现的情况下,你不能说任何关于操作的线程安全的聪明的东西。 这不仅仅是使用的处理器,而是编译器,操作系统和线程模型的组合。

在没有相反的文档的情况下,我不会认为任何操作都是线程安全的,特别是对于多核处理器(或多处理器系统)。 我也不信任testing,因为线程同步问题很可能只是偶然出现。

没有什么是线程安全的,除非你有文档说明它是针对你正在使用的特定系统的。

根据x86上的这个汇编课程 ,你可以primefaces地添加一个寄存器到一个内存位置 ,所以你的代码可能会自动执行“++ i”或“i ++”。 但正如另一篇文章所说,C ansi不会将primefaces性应用于'++'操作,所以你不能确定你的编译器会产生什么。

我认为,如果expression式“i ++”在语句中是唯一的,那么它就相当于“++ i”,编译器足够聪明,不会保留一个时间值等等。所以如果你可以互换地使用它们(否则你赢了不要问是哪一个使用),无论你使用什么都不重要,因为它们几乎是一样的(美学除外)。

无论如何,即使增量操作符是primefaces的,如果不使用正确的锁,也不能保证其余的计算是一致的。

如果你想自己试验,写一个程序,其中N个线程同时增加一个共享variablesM次…如果该值小于N * M,那么一些增量被覆盖。 尝试用preincrement和postincrement,并告诉我们;-)

对于计数器,我推荐使用比较和交换方式,它既是非locking的也是线程安全的。

这是在Java中:

 public class IntCompareAndSwap { private int value = 0; public synchronized int get(){return value;} public synchronized int compareAndSwap(int p_expectedValue, int p_newValue){ int oldValue = value; if (oldValue == p_expectedValue) value = p_newValue; return oldValue; } } public class IntCASCounter { public IntCASCounter(){ m_value = new IntCompareAndSwap(); } private IntCompareAndSwap m_value; public int getValue(){return m_value.get();} public void increment(){ int temp; do { temp = m_value.get(); } while (temp != m_value.compareAndSwap(temp, temp + 1)); } public void decrement(){ int temp; do { temp = m_value.get(); } while (temp > 0 && temp != m_value.compareAndSwap(temp, temp - 1)); } } 

把我扔进线程本地存储; 它不是primefaces的,但它并不重要。

你说“这只是一个指令,不会因上下文切换而中断”。 – 这对一个CPU来说是非常好的,但是双核CPU呢? 那么你可以真正有两个线程同时访问同一个variables,而不需要任何上下文切换。

不知道这个语言,答案就是testing一下。