繁体   English   中英

std :: shared_ptr复制构造函数线程安全

[英]std::shared_ptr copy constructor thread safety

std :: shared_ptr规范保证只有一个线程将在内部指针上调用delete。 这个答案对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);

}

如果正确使用了shared_ptr ,描述将永远不会发生。

要从中复制的shared_ptr在传递给复制构造函数之前已使refcount递增,并且直到复制构造函数退出后才能对其进行销毁,因为它是构造函数的局部参数。

因此,另一个线程不会破坏正在共享的对象。 的引用次数_Other.Rep永远是至少1在进入拷贝构造函数,如果_Other.Rep不为空。

更新 :您的用例是错误的。 lambda捕获对主线程的shared_ptr实例的引用 ,但该线程不会复制该shared_ptr直到它已经超出范围并被main破坏为止。 您的线程有一个悬空的引用,导致您的代码具有未定义的行为 这不是shared_ptr实现的错误。 您的lambda需要通过值而不是通过引用来捕获shared_ptr ,因此其refcount在创建线程之前立即增加,而不是在线程开始运行时增加。

shared_ptr对象共享的状态的操作是线程安全的; shared_ptr 本身不是线程安全的。 您不能同时在不同线程中操作同一个shared_ptr对象。 尝试这样做是为了进行数据竞赛,因此是UB。

因此,如果lambda在将指针运送到另一个线程之前复制了指针,则您的代码会很好。

还应注意,无论如何编写shared_ptr ,您的特定示例都将无法正常工作。 类型可以是atomic<int> ,并且仍然会损坏。 您为lambda提供了对可能在lambda执行复制操作之前可能不存在的对象的引用。

内部线程安全无处可挡。 将对堆栈变量的引用传递给另一个线程时,应始终将其视为代码气味。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM