Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Race condition example about shared_ptr

Why there is no race condition in the code snippet below,

#include<memory>
#include<thread>

std::shared_ptr<int> g_s = std::make_shared<int>(1);

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

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

int main()
{
    std::thread(f2).join();
}

whereas there is a race condition in the code snippet below?

#include<memory>
#include<thread>

std::shared_ptr<int> g_s = std::make_shared<int>(1);

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

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

int main()
{
    std::thread(f2).join();
}

My current thought about this question is seen at the first answer. But I am not so sure yet. Could somebody please shed some light this matter?

like image 497
John Avatar asked Oct 23 '25 22:10

John


2 Answers

std::shared_ptr itself is not thread-safe, meaning that Same object (instance) of std::shared_ptr<> can't be modified same time from two threads.

It is not thread safe because there is no mutex inside implementation of shared_ptr<>, for the sake of speed.

Your first code snippet passes around only copies of shared_ptr<> instance, never references, hence two threads modify different copies of them. Which is thread safe.

Second code snippet passes reference to second thread, hence two threads modify same instance of shared_ptr<> which is not thread safe.

Why modifying a copy of shared pointer is thread safe? We need to look into how it is implemented.

Shared pointer consists of object's pointer itself and the pointer which points to the structure that conatins two counters, one counts number of references of shared pointer, second counts number of weak pointer references. Counters are usually allocated on Heap, so that pointer to same heaped counters can be shared between several copies of shared pointer.

When copy of shared pointer is made, heap pointer to its counters is copied to other copy of shared pointer, and shared pointer counter is incremented Atomically.

When copy of shared pointer is destroyed, its counter is decremented Atomically. And if it was very last copy then counters are deallocated from heap.

Why counter should be incremented/decremented Atomically? Because same time other copy of shared pointer in other thread might be constructed or destroyed, hence same time other thread will increment/decrement counter.

If counter is incremented/decremented on different threads same time, then it should happen Atomically, so that value is read-modified-stored as one single operation. This is needed to avoid race conditions within counter itself.

Atomic operations are usually done with the help of std::atomic.

like image 147
Arty Avatar answered Oct 26 '25 12:10

Arty


Firstly, std::shared_ptr guarantees access to underlying control block is thread safe.

For the latter code snippet, both the raw pointer and the pointer to the control block may be concurrently read from and written to by different threads, which would constitute a race.

For the former one, since void f1(std::shared_ptr<int> sp) passes the parameter(i.e std::shared_ptr<T> sp) by value, reading both the raw pointer and the pointer to the control block of sp is independent from assigning the said pointers to g_s. So there is no data race condition.

like image 36
John Avatar answered Oct 26 '25 12:10

John