2016-02-26 204 views
2

我有這個多線程的代碼,它試圖使用stl unordered_map創建一個線程本地單例對象。多線程訪問unordered_map的運行時錯誤

這是code。我在這裏複製的代碼逐字:

#include <iostream> 
#include <unordered_map> 
#include <vector> 
#include <thread> 
#include <algorithm> 
#include <mutex> 
using namespace std; 

class single 
{ 
public: 
    // Every thread needs to call this to get its individual instance 
    static single* getInstance(unsigned int threadId); 

    static void print(unsigned int threadId) 
    { 
     std::cout << "threadId:" << threadId << ", Singleton: " << _instances[threadId] << std::endl; 
    } 

protected: 
    // Made protected not private so that singleton can be subclassed 
    single();           // Clients cant construct objects directly 
    ~single();           // cannot be destroyed by clients 

    single(const single &) = delete;     // non-copyable 
    single& operator=(const single &) = delete;   // can't be copy assigned 
    single(single &&) = delete;       // non-move constructible 
    single & operator=(single &&) = delete;   // non-move assignable 

private: 
    static std::unordered_map<unsigned,single*> _instances; 
    static std::mutex _lock; 

}; 

std::mutex single::_lock; 
std::unordered_map<unsigned,single*> single::_instances; 

single::single(){} 

single::~single(){} 

single* single::getInstance(unsigned int threadId) 
{ 
    if(_instances.count(threadId) == 0) 
    { 
     std::lock_guard<std::mutex> lg(_lock); 
     if(_instances.count(threadId) == 0) 
     { 
      _instances[threadId] = new single; 
      std::cout <<"Created By ThreadId: " << threadId <<std::endl; 
     } 
    } 

    return _instances[threadId]; 
} 

void Run(unsigned int threadId) 
{ 
    single::getInstance(threadId)->print(threadId); 
} 

int main() 
{ 
    std::vector<std::thread> workers; 
    const unsigned threadCount = 16; 

    for(auto i = 0; i != threadCount; ++i) 
    { 
     workers.push_back(std::thread(Run, i)); 
    } 

    for_each(workers.begin(), workers.end(), std::mem_fn(&thread::join)); 

    return 0; 
} 

我用unordered_mapcount()常數函數(線程安全的?),以檢查是否該線程的實例被創建。如果計數爲0,則創建實例並將其作爲密鑰的值存儲爲threadId。我還添加了一個lock_guard以防止多線程併發插入到無序的映射靜態對象,但它似乎是一些競爭條件,我無法發現並且此代碼有時會出錯。

任何人都可以向我解釋在這段代碼的哪部分競爭條件正在發生,如果這個解決方案可以使線程本地單身人士工作?

回答

3

誰能給我解釋這其中的部分代碼的比賽條件正在發生的事情...

不,這不是線程安全的。一旦至少有一位作家,所有訪問都需要通過鎖來保護。所以第一個_instances.count()是無人防守的,可能導致數據競爭。

...如果這個解決方案可以使線程本地單例工作?

你可以看看使用thread_local控制本地線程存儲時限。

您也可以將防盜鎖移動到unordered_map的任何訪問之前。

single* single::getInstance(unsigned int threadId) 
{ 
    std::lock_guard<std::mutex> lg(_lock); 
    if(_instances.count(threadId) == 0) 
    { 
     _instances[threadId] = new single; 
     std::cout <<"Created By ThreadId: " << threadId <<std::endl; 
    } 
    return _instances[threadId]; 
} 
的線程數

Demo code here.


側面說明,根據問題正在得到解決,你可以線程數限制爲std::thread::hardware_concurrency限制。

+0

好吧,我提到count()是一個常量函數。它不是線程安全的,因爲它只讀取和不修改地圖狀態? – nurabha

+2

'const'意味着'count()'函數不會改變容器的內容,並不意味着其他函數不會同時更改內容(數據爭用)。 – Niall

+0

關於您的代碼遷移建議,我最初是這麼做的。但它不起作用。所以我想我應該嘗試使用雙重檢查鎖定來防止競爭條件_instances.count() – nurabha