锁前后检查资源

Posted

技术标签:

【中文标题】锁前后检查资源【英文标题】:Check resource before and after the lock 【发布时间】:2021-09-11 20:49:28 【问题描述】:

我遇到过类似这样的简化代码

inline someClass* otherClass::getSomeClass()

    if (m_someClass)
        return m_someClass.get();

    std::unique_lock<std::shared_mutex> lock(m_lock);
    if (m_someClass)
        return m_someClass.get();

    m_someClass= std::make_unique<someClass>(this);
    return m_someClass.get();

因此,确保创建 someClass 对象的线程安全似乎是一种模式。我在多线程方面没有太多经验,但是这段代码对我来说并不好看。是否有其他方法可以重写它或者它应该是一种方式?

【问题讨论】:

见double-checked-locking 我认为这里的重要问题是当一个线程调用getSomeClass而没有获得锁而另一个线程在getSomeClass中间并构造一个持有锁的新对象时会发生什么。我怀疑是UB。 另外我对 Most Vexing Parse 不是很熟悉,但 std::unique_lock&lt;std::shared_mutex&gt; lock(m_lock); 可能根本没有效果,应该重写为看起来不像函数定义,例如auto lock = std::unique_lock&lt;std::shared_mutex&gt;m_lock; 甚至 auto lock = std::unique_lockm_lock; 如果您至少启用了 C++17 和 CTAD @alterigel 我认为std::unique_lock&lt;std::shared_mutex&gt; lock(m_lock); 不受MVP 约束,因为m_lock 不能与函数参数的类型名混淆。但无论如何,std::unique_lock&lt;std::shared_mutex&gt; lockm_lock; 就足够了,不需要auto 【参考方案1】:

这里最大的问题是您违反了 C++ 内存模型。在 C++ 内存模型中,对同一数据的写操作和读操作必须同步。

前面的m_someClass 正在读取互斥体中写入的内容。

m_someClass 上的 operator bool 可能在某种程度上是原子的。

此外,您的代码不会处理被销毁的对象。

如果它是原子的,那么您可能应该使用原子操作来更新它而不是锁。这种模式会导致创建“浪费”的对象;通常这值得卸下锁的成本。

m_someClass 设为std::atomic&lt;std::shared_ptr&lt;someClass&gt;&gt;

getSomeClass返回std::shared_ptr&lt;someClass&gt;

auto existing = m_someClass.load();
if (existing)
  return existing;

auto created = std::make_shared<someClass>(this);
if (
  m_someClass.compare_exchange_strong(existing, created)
) 
  return created;
 else 
  return existing;

如果两个线程都尝试同时获取,则它们都可以创建一个新的someClass,但只有一个会持续存在,另一个将被丢弃,函数将返回持续存在的那个。

【讨论】:

以上是关于锁前后检查资源的主要内容,如果未能解决你的问题,请参考以下文章

第二次检查后flock lock丢失

音乐App前后

通过检查条件并重新检查来获取锁

C# 线程 - 锁 - 如何检查锁的出现

检查 IPC 共享锁

检查当前线程是不是拥有锁