当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《我听说i++不是线程安全的,++我是线程安全的吗?》经验,为你挑选了5个好方法。

我听说i ++不是一个线程安全的语句,因为在汇编时它减少了将原始值存储为某个地方的temp,递增它,然后替换它,这可能被上下文切换中断.

但是,我想知道++ i.据我所知,这将减少为单个汇编指令,例如'add r1,r1,1',因为它只有一条指令,所以它不会被上下文切换中断.

任何人都可以澄清吗?我假设正在使用x86平台.



1> paxdiablo..:

你听错了."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的东西synchronizedpthread_mutex_lock()(可下一些操作系统的C/C++),你需要寻找到什么(一).


(a)在C11和C++ 11标准完成之前询问这个问题.这些迭代现在已经在语言规范中引入了线程支持,包括原子数据类型(尽管它们和一般的线程是可选的,至少在C中是可选的).


+1强调这不是特定于平台的问题,更不用说明确答案了......
@Bastien:公牛.RISC处理器通常没有内存增量指令.load/add/stor tripplet就是你在PowerPC上的表现.
祝贺你的C银徽章:)

2> Jim Mischel..:

你不能对++ i或i ++做一个全面的陈述.为什么?考虑在32位系统上递增64位整数.除非底层机器具有四字"加载,递增,存储"指令,否则递增该值将需要多个指令,其中任何指令都可以被线程上下文切换中断.

此外,++i并不总是"添加一个值".在像C这样的语言中,递增指针实际上会增加指向的东西的大小.也就是说,如果i是指向32字节结构的指针,则++i添加32个字节.尽管几乎所有平台都具有原子的"内存地址增量值"指令,但并非所有平台都具有原子"在内存地址处添加任意值"指令.


当然,如果你不把自己局限于无聊的32位整数,用C++之类的语言,++我真的可以调用一个更新数据库中值的web服务.

3> yogman..:

它们都是线程不安全的.

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 ++.它比依赖互斥锁要快得多.


"CPU不能直接用内存做数学" - 这不准确.有CPU-s,您可以在内存元素上"直接"进行数学运算,而无需先将其加载到寄存器中.例如.MC68000

4> Eclipse..:

如果您在多核环境中跨线程共享一个int,则需要适当的内存屏障.这可能意味着使用互锁指令(例如,请参阅win32中的InterlockedIncrement),或者使用可以提供某些线程安全保证的语言(或编译器).使用CPU级别指令重新排序和缓存以及其他问题,除非您有这些保证,否则不要假设跨线程共享的任何内容都是安全的.

编辑:对于大多数体系结构,您可以假设的一件事是,如果您正在处理正确对齐的单个单词,您将不会得到一个单词,其中包含两个被混合在一起的值的组合.如果两个写入发生在彼此之上,则一个将获胜,另一个将被丢弃.如果你小心,你可以利用这一点,并看到++ i或i ++在单一编写器/多读卡器情况下是线程安全的.


我只是说原子性并不能保证线程安全.如果您足够聪明,可以设计无锁数据结构或算法,那么请继续.但是你仍然需要知道编译器给你的保证是什么.

5> Max Lybbert..:

如果你想在C++中使用原子增量,你可以使用C++ 0x库(std::atomic数据类型)或类似TBB的东西.

曾经有一段时间,GNU编码指南说更新适合一个单词的数据类型"通常是安全的",但是对于SMP机器来说建议是错误的,对于某些体系结构来说是错误的,而在使用优化编译器时则是错误的.


澄清"更新单字数据类型"的评论:

SMP计算机上的两个CPU可以在同一周期中写入相同的内存位置,然后尝试将更改传播到其他CPU和缓存.即使只写入一个数据字,因此写入只需要一个周期完成,它们也会同时发生,因此您无法保证哪个写入成功.您不会获得部分更新的数据,但一次写入将消失,因为没有其他方法可以处理此情况.

比较并交换多个CPU之间的正确坐标,但没有理由相信单字数据类型的每个变量赋值都将使用比较和交换.

虽然一个优化编译器不会影响如何加载/存储编译,它可以改变时,加载/存储发生,如果你希望你的读取和写入在它们出现在源代码相同的顺序发生(,造成了严重的麻烦最着名的是双重检查锁定在vanilla C++中不起作用).

注意 我的原始答案还说英特尔64位架构在处理64位数据时被打破.这不是真的,所以我编辑了答案,但我的编辑声称PowerPC芯片坏了. 将立即值(即常量)读入寄存器时也是如此(参见清单2和清单4中名为"加载指针"的两个部分).但是有一个指令用于在一个周期(lmw)中从内存加载数据,所以我删除了我的答案部分.

推荐阅读
云聪京初瑞子_617
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有