3

在 C++ 中,我觉得我一直被引导相信像 var++ 和 var- 这样的东西是合理的线程安全的 - 又名 - 你可以保证你的价值会在某个时间点增加或减少。

正是基于这种信念,我建立了对线程安全操作的非阻塞算法的理解。然而,今天我很震惊,因为我有一个变量没有增加——因此我质疑我过去大量工作的有效性。

在一个小程序中,我有一个初始化为0的全局变量。启动了8个P-Threads,每个调用varname++共1024次,总计8*1024增量。然而,在所有线程执行完毕后,varname 的值明显小于 8*1024。

我错过了这里的船吗?有人可以启发我吗?

4

3 回答 3

5

究竟是什么让你相信那些线程安全的?一般来说,他们不是。原因很简单:算术通常是在寄存器上完成的,因此var++可能会转换为如下所示:

load var to R1
inc R1
store R1 to var

如果另一个线程var在加载和存储之间进行了修改,您显然会丢失该更新。实际上这个问题会更糟,因为编译器可以决定将变量保存在寄存器中,只要它想要(只要它可以证明它var不是通过任何指向它的指针访问的(在同一线))。

让多个线程访问同一个变量被(C++11)标准定义为数据竞争(因此是未定义的行为),除非没有线程修改变量(如果所有线程都只进行读取访问,你很好) .

对于线程安全操作,您需要使用锁定(例如std::mutex在 C++11 中使用)或原子操作。如果您使用 C++11,您可以使用std::atomic<int>(或您的计数器的任何类型)作为var获取线程安全修改的类型。(算术)操作std::atomic<T>(如递增和递减运算符)被标准保证是线程安全的。

于 2013-03-23T22:26:03.630 回答
4

C++ 的前置和后置增量运算符不是线程安全的。

这里有类似的问题: 我听说 i++ 不是线程安全的,++i 是线程安全的吗?

于 2013-03-23T22:23:59.983 回答
2

是的,你错过了一些东西——即你的读写不是原子的。因此,许多线程可以读取该值,然后将其递增,然后将其写回,如果这些操作都“并行”发生,则该值只会递增 1。

您可以使用 C++11(或 Boost 的)std::atomic 类型包装器来解决此问题,如下所述:http: //en.cppreference.com/w/cpp/atomic/atomic

于 2013-03-23T22:24:18.877 回答