[英]std::shared_ptr copy constructor thread safety
std::shared_ptr specification guarentees that only one thread will invoke delete on the internal pointer. std :: shared_ptr规范保证只有一个线程将在内部指针上调用delete。 This answer has a really nice explanation about the required memory ordering on the shared_ptr refrence count manipulation in order to guarentee that the delete will be invoked on a synchronized memory.
这个答案对shared_ptr刷新计数操作上所需的内存顺序进行了很好的解释,以确保删除将在同步的内存上进行。
What I don't understand is the following: 我不明白的是以下内容:
I am looking at MVCC implementation of shared_ptr copy constructor. 我正在看shared_ptr复制构造函数的MVCC实现。 and I think I can identify at least one race condition.
而且我认为我至少可以确定一种比赛条件。
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;
}
The implementation checks that the control block is valid, then incerement its reference count, and copy assigns the internal fields. 该实现检查控制块是否有效,然后确定其引用计数,并复制分配内部字段。
Assuming _Other
is owned by a different thread then the one calling the copy constructor. 假设
_Other
由一个不同的线程拥有,然后调用副本构造函数。 If between the lines if (_Other._Rep)
and _Other._Rep->_Incref();
如果在两行之间
if (_Other._Rep)
和_Other._Rep->_Incref();
this thread calls the destructor that happens to delete the control block and the pointer, then _Other._Rep->_Incref()
will dereference a deleted pointer. 该线程调用恰好删除控制块和指针的析构函数,然后
_Other._Rep->_Incref()
将取消引用已删除的指针。
Here is a code that illustrates the corner case I am talking about. 这是一个代码,说明了我正在谈论的特殊情况。 I will tweak share_ptr copy constructor implementation to simulate a context switch:
我将调整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;
}
And here is a code that will probably show the problem: 这是一个可能会显示问题的代码:
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);
}
When shared_ptr
is used properly , what your describe can never happen. 如果正确使用了
shared_ptr
,描述将永远不会发生。
The shared_ptr
that is being copied from has incremented the refcount before being passed to the copy constructor, and it can't be destructed until after the copy constructor has exited since it is a local parameter of the constructor. 要从中复制的
shared_ptr
在传递给复制构造函数之前已使refcount递增,并且直到复制构造函数退出后才能对其进行销毁,因为它是构造函数的局部参数。
Thus, another thread will not destroy the object that is being shared. 因此,另一个线程不会破坏正在共享的对象。 The refcount of
_Other.Rep
will always be at least 1 upon entry into the copy constructor if _Other.Rep
is not null. 的引用次数
_Other.Rep
永远是至少1在进入拷贝构造函数,如果_Other.Rep
不为空。
UPDATE : your use case is faulty. 更新 :您的用例是错误的。 The lambda captures a reference to the main thread's
shared_ptr
instance but the thread does not make a copy of that shared_ptr
until after it has already gone out of scope and been destroyed by main
. lambda捕获对主线程的
shared_ptr
实例的引用 ,但该线程不会复制该shared_ptr
直到它已经超出范围并被main
破坏为止。 Your thread has a dangling reference causing your code to have undefined behavior . 您的线程有一个悬空的引用,导致您的代码具有未定义的行为 。 That is not a fault of the
shared_ptr
implementation. 这不是
shared_ptr
实现的错误。 Your lambda needs to capture the shared_ptr
by value instead of by reference instead, so its refcount is incremented immediately, before the thread is created, not when the thread starts running. 您的lambda需要通过值而不是通过引用来捕获
shared_ptr
,因此其refcount在创建线程之前立即增加,而不是在线程开始运行时增加。
Manipulation of the state shared by shared_ptr
objects is thread-safe; 由
shared_ptr
对象共享的状态的操作是线程安全的; shared_ptr
itself is not thread-safe. shared_ptr
本身不是线程安全的。 You cannot manipulate the same shared_ptr
object from different threads at the same time; 您不能同时在不同线程中操作同一个
shared_ptr
对象。 attempting to do so is a data race and thus UB. 尝试这样做是为了进行数据竞赛,因此是UB。
So your code would be fine if lambda
copied the pointer before being shipped off to another thread. 因此,如果
lambda
在将指针运送到另一个线程之前复制了指针,则您的代码会很好。
It should also be noted that your specific example could never work, no matter how shared_ptr
was written. 还应注意,无论如何编写
shared_ptr
,您的特定示例都将无法正常工作。 The type could be atomic<int>
and it would still be just as broken. 类型可以是
atomic<int>
,并且仍然会损坏。 You gave the lambda a reference to an object that may not exist before the lambda gets to execute the copy operation. 您为lambda提供了对可能在lambda执行复制操作之前可能不存在的对象的引用。
No amount of internal thread-safety can save you there. 内部线程安全无处可挡。 Passing a reference to a stack variable to another thread should always be looked on as a code smell.
将对堆栈变量的引用传递给另一个线程时,应始终将其视为代码气味。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.