2016-10-03 27 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