x++; 是线程安全的吗?

5
如果我在一个线程中像这样更新一个变量: receiveCounter ++;
然后从另一个线程中,我只读取此变量并将其值写入GUI。
这样安全吗?或者这个指令可能会在中间被打断,因此当另一个线程读取时,receiveCounter中的值是错误的?是的,因为++不是原子操作,它是几个指令。
我不关心同步读写,它只需要被递增,然后在GUI中更新,但这两个操作并不必须直接相连。
我关心的是该值不能错误。比如++操作在中途被中断,所以读取的值完全不正确。
我需要锁定这个变量吗?我真的不想这样做,因为它经常被更新。我可以通过向主线程发送消息并将值复制到队列来解决这个问题(然后需要锁定队列,但我不会在每次更新时都这样做)。
但无论如何,我对上述问题感兴趣。

6
你说的是哪种语言? - bash.d
这可能会有用:https://dev59.com/F3RB5IYBdhLWcg3wQFPu - Magnus Grindal Bakken
6个回答

3

如果一个线程改变了一个变量的值,而另一个线程读取了该值,并且程序没有同步访问,那么就会出现数据竞争,程序的行为是不确定的。将receiveCounter的类型改为std::atomic<int>(假设它一开始就是一个int)。


2
在其核心,它是一个读-修改-写操作,它们不是原子性的。有一些处理器拥有专门的指令来执行此操作,如 Intel/AMD 核心,非常普遍,它们有 INC 指令。
虽然听起来似乎是原子性的,因为它只是一个指令,但实际上它并不是原子性的。x86/x64 指令集与执行引擎实际上的实现方式关系不大。执行引擎使用类似于 RISC 的“微操作”,INC 指令被翻译成多个微操作。如果在指令上添加 LOCK 前缀,则可以将其设置为原子性。但编译器只有在知道需要进行原子更新时才会发出此指令。
因此,您需要明确表达出来。C++11 std::atomic<> 标准是一个很好的方式。您的操作系统或编译器将为此提供内置函数,通常命名为“Interlocked”或“__built_in”。

我理解这个问题是只有读取部分和写入部分需要原子性,而不是整个读取-修改-写入过程。这难道不更多地涉及到对齐和数据大小的问题吗? - adrianm

1
简单回答:不是。
因为 i++;i = i + 1; 是相同的,它们包含了加载、数学运算和保存值。所以一般来说不是原子操作。
然而,真正执行的操作取决于 CPU 的指令集,并且根据 CPU 架构的不同,可能是原子操作。但默认情况下仍不是原子操作。

1
正确的答案,错误的推理。在某些处理器上可以用单个指令实现。然而,由于问题中未指定处理器,并且C++标准未指定操作为原子操作,因此它不是线程安全的。 - user207421

0

一般来说,它不是线程安全的,因为++运算符包含一个读取和一个写入操作,这对操作不是原子性的,可能会在中间被打断。

然后,这也可能取决于语言/编译器/架构,但在典型情况下,增量操作可能不是线程安全的。

(已编辑)

至于读取写入操作本身,只要您不处于64位模式下,它们应该是原子性的,因此如果您不关心其他线程将值错误地增加1,那么对于您的情况可能是可以的。


不仅如此,有时候即使结果不正确也是可以接受的。例如,如果GUI线程正在轮询变量,则在短短的一秒钟内出现错误的值可能是可以接受的。 - Some programmer dude
确实如@JoachimPileborg所说。对于每秒更新一次的进度计数器/进度条等,所看到的值总是会有一定程度的过时,那么为什么要担心偶尔的小问题呢?在许多处理器上,++最终都会变成单指令INC。 - Martin James

0

++ 相当于 i=i+1

++ 不是一个 原子操作,因此它是 线程安全的。只有基本变量(除了 long 和 double)的读取和写入是原子的,因此是线程安全的。


不,i++并不等同于i = i+1 - user207421
@EJP,您能否对上面的评论进行更详细的说明? - rai.skumar
只有原始变量(除了long和double)的读写是原子性的。C++11甚至不能保证这一点。如果您想要原子性和线程安全,您需要使用std::atomic,没有捷径可走。 - Nate Eldredge

0

不,递增操作不是原子操作,因此不是线程安全的。

在您的情况下,如果您不关心特定时间内的值(如果您只从另一个线程读取此变量而不尝试写入它),则使用此操作是安全的。它最终会递增receiveCounter的值,但您无法保证操作顺序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接