Jeff Preshing的这个article声明双重检查锁定模式(DCLP)在C ++ 11中是固定的。用于此模式的经典示例是单例模式,但我碰巧有不同的用例,我仍缺乏处理经验" atomic<>武器" - 也许这边的人可以帮助我。
以下一段代码是否是杰夫在"Using C++11 Sequentially Consistent Atomics"下所描述的正确的DCLP实现?
class Foo {
std::shared_ptr<B> data;
std::mutex mutex;
void detach()
{
if (data.use_count() > 1)
{
std::lock_guard<std::mutex> lock{mutex};
if (data.use_count() > 1)
{
data = std::make_shared<B>(*data);
}
}
}
public:
// public interface
};
答案 0 :(得分:2)
不,这不是DCLP的正确实现。
问题是外部检查 data.use_count() > 1
访问对象(类型B with reference count
),可以删除(未引用)互斥保护部分。任何类型的记忆围栏都无法帮助。
为什么 data.use_count()访问对象:
假设已执行这些操作:
shared_ptr<B> data1 = make_shared<B>(...);
shared_ptr<B> data = data1;
然后您有以下布局(此处未显示weak_ptr
支持):
data1 [allocated with B::new()] data
--------------------------
[pointer type] ref; --> |atomic<int> m_use_count;| <-- [pointer type] ref
|B obj; |
--------------------------
每个shared_ptr
对象只是一个指针,指向已分配的内存区域。该内存区域嵌入了B
类型的对象加上原子计数器,反映了指向给定对象的shared_ptr
的数量。当此计数器变为零时,释放内存区域(并且B
对象被销毁)。确切地说,此计数器由shared_ptr::use_count()
返回。
UPDATE :执行,可以导致访问已经释放的内存(最初,两个shared_ptr指向同一个对象,.use_count()
是2):
/* Thread 1 */ /* Thread 2 */ /* Thread 3 */
Enter detach() Enter detach()
Found `data.use_count()` > 1
Enter critical section
Found `data.use_count()` > 1
Dereference `data`,
found old object.
Unreference old `data`,
`use_count` becomes 1
Delete other shared_ptr,
old object is deleted
Assign new object to `data`
Access old object
(for check `use_count`)
!! But object is freed !!
外部检查应该只使用指向对象的指针来决定是否需要获取锁定。
顺便说一句,即使你的实施也是正确的,它有一点意义:如果可以同时从多个线程访问data
(和detach
),对象的唯一性没有任何优势,因为它可以从几个线程访问。如果要更改对象,则对data
的所有访问都应受外部互斥锁的保护,在这种情况下,detach()
不能同时执行。
如果data
(和detach
)只能由同一个线程访问,detach
实现根本不需要任何锁定。< / p>
答案 1 :(得分:1)
如果两个线程同时在同一detach
实例上调用Foo
,则构成数据争用,因为std::shared_ptr<B>::use_count()
(只读操作)将与{{1}同时运行移动赋值运算符(一种修改操作),它是一个数据争用,因此是未定义行为的原因。如果std::shared_ptr<B>
实例永远不会同时访问,另一方面,没有数据竞争,但是Foo
在您的示例中将是无用的。问题是:std::mutex
的指针如何首先被分享?如果没有这一重要信息,即使从不同时使用data
,也很难判断代码是否安全。
答案 2 :(得分:0)
根据您的消息来源,我认为您仍需要在第一次测试之前和第二次测试之后添加线程围栏。
std::shared_ptr<B> data;
std::mutex mutex;
void detach()
{
std::atomic_thread_fence(std::memory_order_acquire);
if (data.use_count() > 1)
{
auto lock = std::lock_guard<std::mutex>{mutex};
if (data.use_count() > 1)
{
std::atomic_thread_fence(std::memory_order_release);
data = std::make_shared<B>(*data);
}
}
}