【发布时间】:2019-01-20 18:55:40
【问题描述】:
std::shared_ptr 规范保证只有一个线程会在内部指针上调用 delete。 这个answer 对 shared_ptr 引用计数操作所需的内存排序有一个非常好的解释,以保证删除将在同步内存上调用。
我不明白的是:
- 如果 shared_ptr 由复制构造函数初始化,是 保证它将为空或有效的 shared_ptr?
我正在查看 shared_ptr 复制构造函数的 MVCC 实现。我想我至少可以识别出一种竞争条件。
template<class _Ty2>
void _Copy_construct_from(const shared_ptr<_Ty2>& _Other)
{ // implement shared_ptr's (converting) copy ctor
if (_Other._Rep)
{
_Other._Rep->_Incref();
}
_Ptr = _Other._Ptr;
_Rep = _Other._Rep;
}
实现检查控制块是否有效,然后增加其引用计数,并复制分配内部字段。
假设_Other 由不同的线程拥有,然后是调用复制构造函数的线程。如果在if (_Other._Rep) 和_Other._Rep->_Incref(); 行之间,该线程调用了恰好删除控制块和指针的析构函数,那么_Other._Rep->_Incref() 将取消引用已删除的指针。
进一步说明
这是一个说明我正在谈论的极端情况的代码。 我将调整 share_ptr 复制构造函数实现以模拟上下文切换:
template<class _Ty2>
void _Copy_construct_from(const shared_ptr<_Ty2>& _Other)
{ // implement shared_ptr's (converting) copy ctor
if (_Other._Rep)
{
// now lets put here a really long loop or sleep to simulate a context switch
int count = 0;
for (int i = 0; i < 99999999; ++i)
{
for (int j = 0; j < 99999999; ++j)
{
count++;
}
}
// by the time we get here, the owning thread may already destroy the shared_ptr that was passed to this constructor
_Other._Rep->_Incref();
}
_Ptr = _Other._Ptr;
_Rep = _Other._Rep;
}
下面是一个可能会显示问题的代码:
int main()
{
{
std::shared_ptr<int> sh1 = std::make_shared<int>(123);
auto lambda = [&]()
{
auto sh2 = sh1;
std::cout << sh2.use_count(); // this prints garbage, -572662306 in my case
};
std::thread t1(lambda);
t1.detach();
// main thread destroys the shared_ptr
// background thread probably did not yet finished executing the copy constructor
}
Sleep(10000);
}
【问题讨论】:
-
您是否建议可以在一个线程中复制一个对象,而在另一个线程中销毁同一对象?
-
按照这个标准,互斥锁也不是“线程安全的”...
-
"现在让我们在这里放一个很长的循环或休眠来模拟上下文切换"
int count = 0;让它volatile:编译器只期望产生相同的结果程序的可观察行为,即与外部世界的交互与 C++ 规范所描述的虚拟机执行的交互相同。 空循环根本没有可观察的行为,可以将其删除。 根据定义,对volatile限定对象的访问是可观察的,因此无法删除访问此类对象的循环。
标签: c++ multithreading thread-safety shared-ptr smart-pointers