锁前后检查资源
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<std::shared_mutex> lock(m_lock);
可能根本没有效果,应该重写为看起来不像函数定义,例如auto lock = std::unique_lock<std::shared_mutex>m_lock;
甚至 auto lock = std::unique_lockm_lock;
如果您至少启用了 C++17 和 CTAD
@alterigel 我认为std::unique_lock<std::shared_mutex> lock(m_lock);
不受MVP 约束,因为m_lock
不能与函数参数的类型名混淆。但无论如何,std::unique_lock<std::shared_mutex> lockm_lock;
就足够了,不需要auto
。
【参考方案1】:
这里最大的问题是您违反了 C++ 内存模型。在 C++ 内存模型中,对同一数据的写操作和读操作必须同步。
前面的m_someClass
正在读取互斥体中写入的内容。
m_someClass
上的 operator bool
可能在某种程度上是原子的。
此外,您的代码不会处理被销毁的对象。
如果它是原子的,那么您可能应该使用原子操作来更新它而不是锁。这种模式会导致创建“浪费”的对象;通常这值得卸下锁的成本。
将m_someClass
设为std::atomic<std::shared_ptr<someClass>>
。
从getSomeClass
返回std::shared_ptr<someClass>
。
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
,但只有一个会持续存在,另一个将被丢弃,函数将返回持续存在的那个。
【讨论】:
以上是关于锁前后检查资源的主要内容,如果未能解决你的问题,请参考以下文章