2016-10-03 76 views
0

我正在尝试创建一个线程安全的shared_ptr类。我的用例是shared_ptr属于类的一个对象,其行为有点像单例(CreateIfNotExist函数可以在任何时间点由任何线程运行)。正确创建thread_safe shared_ptr而不锁定的方法?

本质上,如果指针为空,则设置其值的第一个线程将胜出,同时创建它的所有其他线程将使用获胜线程的值。

这里是我到目前为止(注意,问题的唯一功能是CreateIfNotExist()函数,其余是用于测试目的):

#include <memory> 
#include <iostream> 
#include <thread> 
#include <vector> 
#include <mutex> 

struct A { 
    A(int a) : x(a) {} 
    int x; 
}; 

struct B { 
    B() : test(nullptr) {} 

    void CreateIfNotExist(int val) { 
     std::shared_ptr<A> newPtr = std::make_shared<A>(val); 
     std::shared_ptr<A> _null = nullptr; 
     std::atomic_compare_exchange_strong(&test, &_null, newPtr); 
    } 

    std::shared_ptr<A> test; 
}; 

int gRet = -1; 
std::mutex m; 

void Func(B* b, int val) { 
    b->CreateIfNotExist(val); 
    int ret = b->test->x; 

    if(gRet == -1) { 
     std::unique_lock<std::mutex> l(m); 
     if(gRet == -1) { 
      gRet = ret; 
     } 
    } 

    if(ret != gRet) { 
     std::cout << " FAILED " << std::endl; 
    } 
} 

int main() { 
    B b; 

    std::vector<std::thread> threads; 
    for(int i = 0; i < 10000; ++i) { 
     threads.clear(); 
     for(int i = 0; i < 8; ++i) threads.emplace_back(&Func, &b, i); 
     for(int i = 0; i < 8; ++i) threads[i].join(); 
    } 
} 

这是这样做的正确方法?有没有更好的方法来确保所有调用CreateIfNotExist()的线程同时都使用相同的shared_ptr?

+0

为什么不把'B'不是默认contructable所以'test'必须是有效的? – NathanOliver

+0

只需定义一个单独的共享指针并在产生它时将它传递给每个线程? – sji

+0

@sji我故意以这样的方式构建代码,以适合我的用例,如果没有大量的重构,就不可能做到这一点 – Andrew

回答

4

沿东西也许这些线路:

struct B { 
    void CreateIfNotExist(int val) { 
    std::call_once(test_init, 
        [this, val](){test = std::make_shared<A>(val);}); 
    } 

    std::shared_ptr<A> test; 
    std::once_flag test_init; 
}; 
+0

这种方法的轻量化程度如何?比较和交换会更有效率吗? – Andrew

+1

这取决于你的用例。你为'once_flag'创建更大的结构,但是保证只创建一个结构。另一方面,这个问题的方法不会夸大这个结构,但是可以'''几乎同时构造'A',仅仅丢弃其中的一个。哪个更适合你,取决于你。 – krzaq

+0

假设我也有要求,我可以在任何时候将shared_ptr设置为nullptr,这种方法是否可行? – Andrew