About thread-safety of weak_ptr

Viewed 13687
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
}

Regarding the code above, I know different threads reading and writing the same shared_ptr leads to race conditions. But how about weak_ptr? Is there any race condition in the code below? (My platform is 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)
}
5 Answers

For brevity in the following discussion, different weak_ptrs and shared_ptrs that all are generated from the same original shared_ptr or unique_ptr will be termed 'instances'. weak_ptrs and shared_ptrs that don't share the same object do not need to be considered in this analysis. The general rules for assessing thread safety are:

  1. Simultaneous const member function calls on the same instance are thread-safe. All observer functions are const.
  2. Simultaneous calls on different instances are thread-safe, even when one of the calls is a modifier.
  3. Simultaneous calls on the same instance when at least one of the calls is a modifier are not thread-safe.

The following table shows thread-safety when two threads are operating on the same instance at the same time.

+---------------+----------+-------------------------+------------------------+
|   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                   |
+---------------+----------+-------------------------+------------------------+

The cppreference discussion of std::atomic(std::weak_ptr) is clearest on the safety of simultaneous accesses to different instances:

Note that the control block used by std::weak_ptr and std::shared_ptr is thread-safe: different non-atomic std::weak_ptr objects can be accessed using mutable operations, such as operator= or reset, simultaneously by multiple threads, even when these instances are copies or otherwise share the same control block internally.

C++20 introduces a std::atomic specialization of weak pointer that provides thread-safe modification of the same instance through appropriate synchronization. Note that when it comes to constructors, initialization from another instance is not atomic. For example, atomic<weak_ptr<T>> myptr(anotherWeakPtr); is not an atomic operation.

So to clear it out for me, I am still not quite sure what happens if reset() is called on a std::shared_ptr, at the same time lock() is called for a std:weak_ptr.

Extremely simplified like this:

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();
}

We assume sharedObject is not sharing its pointer with any other objects.

If the both commands (reset() and lock() ) happens at the exactly same time, I expect that either:

  • The sharedObject is successfully reset and weakObject.lock() returns nullptr, and the sharedObject is deleted from the memory.

  • The sharedObject is successfully reset and weakObject.lock() returns a pointer to sharedObject. When leaseObject looses scope, the sharedObject is deleted from the memory.

If the above code is undefined, I have to replace std:mutex in the Object class I have, to outside of the class, but that may be another question in another thread. ;)

Using weak_ptr and shared_ptr across threads is safe; the weak_ptr/shared_ptr objects themselves aren't thread-safe. You can't read/write to a single smart pointer across threads.

Access to g_s like you did isn't safe, be it a shared_ptr or a weak_ptr.

Related