一个线程设置成员,而另一个线程在其上循环-这个线程不安全吗?

时间:2019-02-20 16:23:39

标签: c++ multithreading optimization

我刚刚在我们的代码库中发现了以下构造(在示例中进行了简化):

class SomeClass
{
  public:
    void setKeepGoing(bool b) { m_keepGoing = b; }
    void setDoAdditionalStuff(bool b) { m_doAdditionalStuff = b; }
    void someLoop()
    {
        while(m_keepGoing) 
        {
            //Do something
            bool doMore = m_doAdditionalStuff;
            if (doMore)
                //Do more things
        }  
    }

  private:
    bool m_keepGoing;
    bool m_doAdditionalStuff;
}

有多个线程,一个线程调用someLoop(),而另一个线程调用setKeepGoing()和/或setDoAdditionalStuff()

现在我下沉的直觉是,这是可怕的线程不安全的。编译器可以很好地优化循环内的m_doAdditionalStuff的读取(因为那里没有更改)甚至m_keepGoing(那里也没有改变)有效地导致了代码的行为:

void someLoop()
{
    if (!m_keepGoing)
        return;
    bool doMore = m_doAdditionalStuff;
    while(true) 
    {
        //Do something
        if (doMore)
            //Do more things
    }  
}

我猜对了吗?

4 个答案:

答案 0 :(得分:5)

如果您有读者作家(或多个作家,只有读者),则需要在多个线程中访问同一变量,而 则使该变量成为原子使用锁(或其他同步原语)。否则,您将发生数据争用,并且程序具有未定义的行为。

答案 1 :(得分:5)

您的怀疑是正确的。如果没有某种同步机制,则无法在多个线程中读写相同的变量。这样做是一场数据竞赛,并且是未定义的行为。

在这种情况下,您可以为std::atomic<bool>m_keepGoing使用m_doAdditionalStuff,以便进行同步。

答案 2 :(得分:2)

是的,这是比赛条件。是的,这是我在许多示例中看​​到的代码,事实是,它可在上与当前的编译器一起在现代英特尔架构上运行-仅仅是因为英特尔提供的强大内存保证可以防止任何读取撕裂或缓存的问题价值,并且编译器通常无法用足够复杂的代码来优化这种访问。

现在,最常见的解决问题的建议是将<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <soapenv:Fault> <faultcode>soapenv:Client</faultcode> <faultstring>Unable to determine type mapping for type Profile. Type is illegal here.</faultstring> </soapenv:Fault> </soapenv:Body> </soapenv:Envelope> 标志替换为bool。不幸的是,实际上说起来容易做起来难。将std::atomic<bool>成员添加到类中使它们不可复制且不可复制构造,因此默认分配和复制构造将不再起作用。这是一个令人讨厌的事情,可能导致编写许多额外的代码,而这些代码将不被支持。可能还需要其他技巧来减轻这种负担。

答案 3 :(得分:2)

如果存在多个访问同一实例的编写器线程,那么您将遇到数据覆盖问题(用于访问实例/变量的自适应线程同步技术应该是原子的)。