2015-06-27 116 views
0

的想法是有實例爲每個線程,所以我創建新實例爲每一個新thread::id這樣的:使用std ::螺紋:: ID在地圖中有線程安全

struct doSomething{ 
    void test(int toto) {} 
}; 

void test(int toto) 
{ 
    static std::map<std::thread::id, doSomething *> maps; 

    std::map<std::thread::id, doSomething *>::iterator it = maps.find(std::this_thread::get_id()); 
    if (it == maps.end()) 
    { 
     // mutex.lock() ? 
     maps[std::this_thread::get_id()] = new doSomething(); 
     it = maps.find(std::this_thread::get_id()); 
     // mutex.unlock() ? 
    } 
    it->second->test(toto); 
} 

這是個好理念?

回答

2

訪問地圖後有一個互斥鎖是不夠的。如果沒有互斥體,則無法到達地圖附近的任何地方,因爲另一個線程在讀取地圖時可能需要互斥體修改地圖。

{ 
    std::unique_lock<std::mutex> lock(my_mutex); 
    std::map<std::thread::id, doSomething *>::iterator it = maps.find(std::this_thread::get_id()); 
    if (it != maps.end()) 
     return *it; 
    auto ptr = std::make_unique<doSomething>(); 
    maps[std::this_thread::get_id()] = ptr.get(); 
    return ptr.release(); 
} 

但除非你有一些特殊/獨特的使用情況下,這是通過thread-local storage一個已經解決的問題,因爲你有C++ 11你有thread_local storage specifier

請注意,此處我使用的是mutex,因爲cout是共享資源,yield只是爲了鼓勵更多的交錯工作流。

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

static std::mutex cout_mutex; 

struct CoutGuard : public std::unique_lock<std::mutex> { 
    CoutGuard() : unique_lock(cout_mutex) {} 
}; 

struct doSomething { 
    void fn() { 
     CoutGuard guard; 
     std::cout << std::this_thread::get_id() << " running doSomething " 
      << (void*)this << "\n"; 
    } 
}; 

thread_local std::unique_ptr<doSomething> tls_dsptr; // DoSomethingPoinTeR 

void testFn() { 
    doSomething* dsp = tls_dsptr.get(); 
    if (dsp == nullptr) { 
     tls_dsptr = std::make_unique<doSomething>(); 
     dsp = tls_dsptr.get(); 
     CoutGuard guard; 
     std::cout << std::this_thread::get_id() << " allocated " 
      << (void*)dsp << "\n"; 
    } else { 
     CoutGuard guard; 
     std::cout << std::this_thread::get_id() << " re-use\n"; 
    } 
    dsp->fn(); 
    std::this_thread::yield(); 
} 

void thread_fn() { 
    testFn(); 
    testFn(); 
    testFn(); 
} 

int main() { 
    std::thread t1(thread_fn); 
    std::thread t2(thread_fn); 
    t2.join(); 
    t1.join(); 
} 

現場演示:http://coliru.stacked-crooked.com/a/3dec7efcb0018549

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out 
140551597459200 allocated 0x7fd4a80008e0 
140551597459200 running doSomething 0x7fd4a80008e0 
140551605851904 allocated 0x7fd4b00008e0 
140551605851904 running doSomething 0x7fd4b00008e0 
140551605851904 re-use 
140551605851904 running doSomething 0x7fd4b00008e0 
140551597459200 re-use 
140551605851904 re-use 
140551597459200 running doSomething 0x7fd4a80008e0 
140551605851904 running doSomething 0x7fd4b00008e0 
140551597459200 re-use 
140551597459200 running doSomething 0x7fd4a80008e0 

這一點很難被發現,但線程 '9200分配..4a80 ..而線程' 1904分配..4b00 ..

+0

這是一個如果C++ 11由於thread_local支持而可用,那麼這個答案很好。如果C++ 11不可用,則線程創建者通過線程參數(指向包含上下文的對象的指針)向其提供所有必需的線程上下文是一種好方法。 – qexyn

+0

大多數編譯器在十多年前都有一些線程本地存儲的變體。 https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C.2B.2B https://msdn.microsoft.com/en-us/library/9w1sdazb(v=vs.71).aspx – kfsone

+0

這就是沒錯,但是沒有C++ 11,TLS就不那麼簡單了,尤其是在編寫跨平臺代碼的時候。這就是爲什麼我建議傳遞上下文對象,因爲與TLS相比,這是一種更簡單的方法,並且每個線程庫都支持將void *傳遞給新線程的概念。 – qexyn

2

不,不是一個好主意。

std::map的方法本身不是線程安全的。

爲了真正使它成爲一個「好主意」,您還必須通過使用互斥鎖或等價物來訪問所有對線程安全的線程。

這不僅包括您已註釋的部分,還包括您正在使用的所有其他方法,如find()。

所有觸及你的std::map的東西都必須受到互斥鎖保護。