【问题标题】:Can not using a mutex to read shared data result in arbitrarily stale data?不能使用互斥锁读取共享数据会导致任意陈旧数据吗?
【发布时间】:2013-01-22 19:48:41
【问题描述】:

假设我们有一个简单的类:

class example
{
    bool m_isCanceled;
    example() : m_isCanceled(false) {}
public:
    void cancel() { m_isCanceled = true; }

    void doWork()
    {
        for (int i = 0; i < MAX_RETRIES; ++i)
        {
            // Slow
            doStuff();

            if (m_isCanceled)
            {
                return;
            }
        }
    }
}

如果我们在一个线程上调用example::doWork(),然后在一段时间后我们在另一个线程上调用example::cancel(),那么在第一个线程看到m_isCanceled 现在为真之前是否存在限制?

在类似的情况下,我建议我们使用互斥锁保护 m_isCanceled,但我的同事说第一个线程最多会在额外的迭代后看到更新。是这样吗?

【问题讨论】:

  • 不,他不对——没有内存栅栏,第一个线程可能永远看到更新。
  • 如果您使用的是 C++11,您应该将 m_isCanceled 声明为 atomic&lt;bool&gt;,如果您不是,则将其访问与互斥锁同步。
  • @Andy :幸运的是,对于那些使用旧工具集的人来说,Boost 1.53 将包含新的 Boost.Atomic 库。 :-]
  • @ildjarn:很高兴听到,感谢您分享信息 :)
  • 您的m_isCanceled 上至少需要volatile - 这应该会阻止它被缓存在循环中的寄存器中。然而,在某些架构上,这还不够,因为缓存不会在另一个内核写入变量时更新——在这种情况下,您必须使用原子类型操作。

标签: c++ multithreading mutex


【解决方案1】:

根本没有保证。理想情况下,您会使布尔变量原子化。如果做不到这一点,让它volatile 恰好可以在几乎所有已知的平台上工作。当然,用互斥锁保护它是可以保证的。

在实践中,无论如何它都会“发生作用”。实现通常不知道doStuff 或它调用的某个函数是否操纵m_isCancelled。因此,它将无法在这些调用中将其保存在寄存器或其他东西中。

【讨论】:

  • 您的第二段不是假定doStuff() 不可内联吗? [我很欣赏上面的评论说“慢”,这可能意味着复杂且不可内联]。
  • 这完全取决于doStuff 的作用。绝对有可能在从未见过变化的真实平台上编写代码。
  • doStuff() 实际上是对成员变量的方法调用,它执行 http 请求。它将一堆成员变量传递给调用,但不是 m_isCanceled。
  • @Bwmat:那么它几乎肯定会“发生作用”。其他线程可以做的任何事情,其中​​一个函数都可以做,除非实现可以内联整个事情,而实际上它不能。但是,谁知道下一代更智能的编译器(或需要更多线程同步的硬件)会做什么。如果您有原子类型,请使用它们。如果性能不重要,请使用互斥锁。如果做不到这一点,请使用volatile,但在已知volatile 足够的情况下使用它是一个依赖于平台的优化。
  • 事实上,这是几乎在所有软件中都能看到的麻烦事。如果 doStuff() 发出一个 http 请求并等待响应,那么当调用 cancel() 时,直到 doStuff() 完成后才会发生取消。这完全违背了 cancel() 的全部目的。当网络中断并且 doStuff() 挂起直到超时期限到期时会发生什么?这个问题困扰着大多数软件!它破坏了可用性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-18
  • 2021-12-22
  • 1970-01-01
  • 2021-10-10
  • 2012-06-29
  • 1970-01-01
相关资源
最近更新 更多