关于weak_ptr的线程安全性

时间:2013-12-20 14:01:22

标签: c++ multithreading c++11 weak-ptr

std::shared_ptr<int> g_s = std::make_shared<int>(1);
void f1()
{
    std::shared_ptr<int>l_s1 = g_s; // read g_s
}

void f2()
{
    std::shared_ptr<int> l_s2 = std::make_shared<int>(3);
    std::thread th(f1);
    th.detach();
    g_s = l_s2; // write g_s
}

关于上面的代码,我知道读取和写入相同shared_ptr的不同线程会导致竞争条件。但weak_ptr怎么样?下面的代码中是否有竞争条件? (我的平台是Microsoft VS2013。)

std::weak_ptr<int> g_w;

void f3()
{
    std::shared_ptr<int>l_s3 = g_w.lock(); //2. here will read g_w
    if (l_s3)
    {
        ;/.....
    }
}

void f4()
{
    std::shared_ptr<int> p_s = std::make_shared<int>(1);
    g_w = p_s;

    std::thread th(f3);
    th.detach();
    // 1. p_s destory will motify g_w (write g_w)
}

4 个答案:

答案 0 :(得分:32)

我知道我迟到了,但是在搜索“weak_ptr线程”时会出现这种情况,而Casey的回答并不是全部真相。 可以在线程中使用shared_ptrweak_ptr,而无需进一步同步。

对于shared_ptr,有很多文档(例如在cppreference.comstackoverflow上)。您可以安全地访问shared_ptr,指向来自不同线程的同一对象。你无法从两个线程敲击相同的指针。换句话说:

// Using p and p_copy from two threads is fine.
// Using p from two threads or p and p_ref from two threads is illegal.
std::shared_ptr<A> p = std::make_shared<A>();
std::shared_ptr<A> &p_ref = p;
std::shared_ptr<A> p_copy = p;

要在代码中解决该问题,请将g_s作为参数(按值)*传递给f1()

对于弱指针,安全保证隐藏在weak_ptr::lock的文档中:

  

有效地返回expired() ? shared_ptr<T>() : shared_ptr<T>(*this),以原子方式执行。

您可以使用weak_ptr::lock()从其他线程获取shared_ptr而无需进一步同步。对于Boost和Chris Jester-Young在here中也确认了this SO answer

同样,您必须确保在从另一个线程访问另一个线程时不要修改相同的weak_ptr,因此也要将g_w传递给f3()

答案 1 :(得分:4)

shared_ptrweak_ptr属于与所有其他标准库类型相同的全局线程安全要求:如果这些成员函数未进行修改,则对成员函数的同时调用必须是线程安全的(const )(详见C ++11§17.6.5.9数据竞争规避[res.data.races])。分配运算符特别是 const

答案 2 :(得分:2)

为简洁起见,在下面的讨论中,全部由相同的原始weak_ptrshared_ptr生成的不同shared_ptrunique_ptr将被称为“实例”。在此分析中无需考虑不共享同一对象的weak_ptrshared_ptr。评估线程安全性的一般规则是:

  1. 在同一实例上同时进行const成员函数调用是线程安全的。所有观察者功能均为const
  2. 在不同实例上同时进行调用是线程安全的,即使其中一个调用是修饰符也是如此。
  3. 如果至少有一个调用是修饰符,则在同一实例上进行的同时调用不是线程安全的。

下表显示了两个线程同时在同一实例上运行时的线程安全性。

+---------------+----------+-------------------------+------------------------+
|   operation   |   type   | other thread modifying  | other thread observing |
+---------------+----------+-------------------------+------------------------+
| (constructor) |          | not applicable          | not applicable         |
| (destructor)  |          | unsafe                  | unsafe                 |
| operator=     | modifier | unsafe                  | unsafe                 |
| reset         | modifier | unsafe                  | unsafe                 |
| swap          | modifier | unsafe                  | unsafe                 |
| use_count     | observer | unsafe                  | safe                   |
| expired       | observer | unsafe                  | safe                   |
| lock          | observer | unsafe                  | safe                   |
| owner_before  | observer | unsafe                  | safe                   |
+---------------+----------+-------------------------+------------------------+

cppreference discussion of std::atomic(std::weak_ptr)在同时访问不同实例的安全性方面最清楚:

  

请注意,std :: weak_ptr和std :: shared_ptr使用的控制块为   线程安全:可以使用以下方式访问不同的非原子std :: weak_ptr对象   多个操作同时进行可变操作,例如operator =或reset   线程,即使这些实例是副本或共享相同的实例   内部控制块。

C ++ 20引入了弱指针的std::atomic特化,它通过适当的同步为同一实例提供线程安全的修改。注意,对于构造函数,从另一个实例进行初始化不是原子的。例如,atomic<weak_ptr<T>> myptr(anotherWeakPtr);不是原子操作。

答案 3 :(得分:0)

所以要为我清除此信息,我仍然不确定如果在std :: shared_ptr上调用reset(),同时为std:weak_ptr调用lock()会发生什么。

极为简化:

std::shared_ptr<Object> sharedObject;
std::weak_ptr<Object> weakObject = sharedObject;

void thread1()
{
    std::shared_ptr<Object> leaseObject = weakObject.lock(); //weakObject is bound to sharedObject
}

void thread2()
{
    sharedObject.reset();
}

我们假定sharedObject不与其他任何对象共享其指针。

如果这两个命令(reset()和lock())完全同时发生,那么我希望两者之一:

  • 共享对象已成功重置,weakObject.lock()返回nullptr,并且该共享对象已从内存中删除。

  • 已成功重置sharedObject,并且weakObject.lock()返回指向sharedObject的指针。当leaseObject失去作用域时,将从内存中删除sharedObject。

如果上面的代码未定义,则必须将我拥有的Object类中的std:mutex替换为该类之外的对象,但这可能是另一个线程中的另一个问题。 ;)