我有以下代码。有什么能使它成为非线程安全的吗?
class runner
{
public:
volatile int exitFlag;
// construct in thread A
runner()
{
exitFlag = 0;
}
// run it in thread B
void threadFunc()
{
// does not matter when the change is getting here
while (exitFlag == 0)
{
// ... do stuff (not using exitFlag)
}
}
// call it from thread A
void signalThread()
{
exitFlag = 1; // only change one bit;
}
};
我创建了一个runner
对象并在另一个线程中启动它threadFunc
,稍后一段时间从第一个线程调用它signalThread
。
我被告知在不同的线程中访问相同的变量(至少它们是写入)可能导致读取垃圾值。但显然在上面的代码中(因为只改变了一位)并不重要。
读/写的顺序也无关紧要。
答案 0 :(得分:7)
不要使用volatile
在线程之间共享变量。它没有给出原子性或同步的适当保证。缺乏原子性不太可能在这里引起特定的问题,虽然正式它会导致不确定的行为。缺乏同步意味着线程完全有可能永远不会看到变化,并继续永远运行。
在C ++ 11或更高版本中,使用std::atomic<int>
。该语言的早期版本没有对线程的标准支持,因此您必须使用编译器提供的任何非标准工具。
答案 1 :(得分:0)
数据成员未受到保护(例如,通过互斥锁)的简单事实使得代码非线程安全。多个线程可以访问同一个runner
实例(例如通过指针或引用),并且可以同时访问exitflag
,因为没有同步机制。
回答评论中的问题:由于int
被定义为CPU&#34; native&#34;类型,我相信给定的读或写操作是单个机器代码指令,不会被另一个线程中断。但我不知道C / C ++是否会保证这种行为。因此,在您的方案中,它可能会起作用,尽管在一般情况下您需要同步。
答案 2 :(得分:0)
为了澄清已经说过的话,虽然有点埋没,但是因为你并不是说你明白了:
当在一个CPU上更改该值时,它通常/通常只会被写入该CPU的缓存中。其他CPU不会看到它 - 其他线程不应该使用完全相同的内存而不应用同步技术!
这个改变后的内存字最终可能会被刷新到主内存中,但这需要多长时间才完全无法预测。此外,如果其他CPU认为他们在自己的缓存中拥有相同的内存字,他们可能不会去寻找另一个副本,直到他们丢弃该内存并稍后再次获取它为止。
互斥锁使用内存屏障&#39;它们基本上是CPU和子系统中的机制,它允许线程说&#34;我希望在此互斥体影响之前和之下看到所有数据的TRUE值。因此,当一个线程获取互斥锁上的锁,修改一块内存并释放互斥锁时,任何其他获取该互斥锁的线程都将保证在该互斥锁的监护下看到所有内存都发生了变化。
您必须在多线程系统中使用互斥锁或其他同步对象,特别是现在处于这个多核CPU时代。回到单核时代,你会想到你提出的建议;当时唯一的风险就是打破原子性。即使是简单的机器字也可能在单个核心机器上遭受破坏的读取/修改/写入循环,更不用说多核......
答案 3 :(得分:-3)
总结评论(主要来自Mike Seymour)
std::atomic
发布的代码包含未定义的行为(由c ++标准声明) 但唯一可能出现的具体问题是线程B没有在合理的时间内得到更改,因为它们被卡在cpu chache中。