简体   繁体   English

关于 shared_ptr 的竞争条件示例

[英]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?有人可以阐明这件事吗?

Firstly, std::shared_ptr guarantees access to underlying control block is thread safe.首先, std::shared_ptr保证对底层控制块的访问是线程安全的。

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(ie 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 .对于前者,由于void f1(std::shared_ptr<int> sp) ) 按值传递参数(即std::shared_ptr<T> sp ),因此读取原始指针和指向sp控制块的指针是独立于将所述指针分配给g_s So there is no data race condition.所以不存在数据竞争条件。

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. std::shared_ptr本身不是线程安全的,这意味着不能从两个线程同时修改 std::shared_ptr<> 的同一对象(实例)。

It is not thread safe because there is no mutex inside implementation of shared_ptr<>, for the sake of speed.它不是线程安全的,因为 shared_ptr<> 的实现内部没有互斥锁,为了速度。

Your first code snippet passes around only copies of shared_ptr<> instance, never references, hence two threads modify different copies of them.您的第一个代码片段仅传递 shared_ptr<> 实例的副本,从不引用,因此两个线程修改它们的不同副本。 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.第二个代码片段传递对第二个线程的引用,因此两个线程修改 shared_ptr<> 的同一实例,这不是线程安全的。

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 .原子操作通常在std::atomic的帮助下完成。

To help the readers who want to better understand about the data race about shared_ptr .帮助希望更好地了解有关shared_ptr的数据竞赛的读者。

This code snippet does not have any race condition:代码段没有任何竞争条件:

#include<memory>
#include<thread>

std::shared_ptr<int> g_s = std::make_shared<int>(1);
std::weak_ptr<int> w_p{g_s};

void f1(std::weak_ptr<int>& wp)
{
    std::shared_ptr<int>l_s1 = wp.lock(); // read g_s
}

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

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

This code snippet below does not have any race condition, either:下面的代码片段也没有任何竞争条件:

#include<memory>
#include<thread>

std::shared_ptr<int> g_s = std::make_shared<int>(1);
std::weak_ptr<int> w_p{g_s};

void f1(std::weak_ptr<int> wp)
{
    std::shared_ptr<int>l_s1 = wp.lock(); // read g_s
}

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

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

Since the std::weak_ptr::lock() creates a new std::shared_ptr that shares ownership of the managed object.由于std::weak_ptr::lock()创建了一个共享托管对象所有权的新 std::shared_ptr。 If there is no managed object, ie *this is empty, then the returned shared_ptr also is empty.如果没有托管对象,即*this 为空,则返回的shared_ptr 也为空。 , wp.Lock() returns a temporary shared_ptr , which is different from the one named g_s . , wp.Lock()返回一个临时的shared_ptr ,它与名为g_s的不同。

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

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