这article Jeff Preshing 指出双重检查锁定模式 (DCLP) 在 C++11 中得到修复。用于此模式的经典示例是单例模式,但我碰巧有一个不同的用例,而且我仍然缺乏处理“原子 <> 武器”的经验 - 也许这里有人可以帮助我。
Jeff 在 "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
};
最佳答案
不,这不是 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
类型的对象加上原子计数器,反射(reflect)了指向给定对象的 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
实现不会根本不需要任何锁定。
关于c++ - 这是带有 shared_ptr 的正确 C++11 双重检查锁定版本吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30911849/