在C ++中,我觉得我总是认为像var ++和var--这样的东西是合理的线程安全的 - AKA - 你可以保证你的价值会在某个时间点增加或减少。
正是基于这种信念,我建立了对线程安全操作的非阻塞算法的理解。 然而,今天我感到震惊,因为我有一个没有增加的变量 - 因此我质疑我过去工作的大量有效性。
在一个小程序中,我有一个初始化为0的全局变量。 启动了8个P-Threads,每个调用varname ++总共1024次,总计增加到8 * 1024。 但是在所有线程完成执行后,varname的值明显小于8 * 1024。
我在这里错过了这艘船吗? 有人可以赐教我吗?
答案 0 :(得分:5)
究竟是什么让你相信哪些线程安全?一般来说,他们不是。原因很简单:算术通常在寄存器上完成,因此var++
可能会转换为如下所示:
load var to R1
inc R1
store R1 to var
如果另一个线程在加载和商店之间修改var
,您显然会松开该更新。实际上这个问题会更糟,因为编译器可以决定将变量保存在寄存器中,只要它能够(只要它可以证明var
不能通过任何指针访问它(在同一个线程中))。
让多个线程访问同一个变量被(C ++ 11)标准定义为数据争用(因此是未定义的行为),除非线程没有修改变量(如果所有线程都只读取访问权限,那么你没事)。
对于线程安全操作,您需要使用锁定(例如在C ++ 11中使用std::mutex
)或原子操作。如果您使用C ++ 11,则可以使用std::atomic<int>
(或您的计数器所使用的任何类型)作为var
的类型来获取线程安全修改。 (算术)std::atomic<T>
上的操作(如增量和减量运算符)保证是标准的线程安全。
答案 1 :(得分:4)
C ++的增量前和增量后运算符不线程安全。
答案 2 :(得分:2)
是的,你错过了什么 - 即你的读写不是原子的。因此,许多线程可以读取该值,然后递增它,然后将其写回,如果这些操作全部“并行”发生,则该值将仅增加1。
您可以使用C ++ 11(或Boost)的std :: atomic类型包装器修复此问题,如下所述: http://en.cppreference.com/w/cpp/atomic/atomic