2013-02-26 80 views
0

我有一個奇怪的行爲與std :: map(或std :: set,他們似乎在這種情況下行爲相同)。 這可能是我對這應該如何工作的嚴重誤解。 我正在使用VS2010 SP1。std :: map多線程中奇怪的資源爭用

就拿這個功能:

extern time_t g_nElapsed; 
UINT Thread(LPVOID _param) 
{ 
    UINT nRuns = (UINT)_param; 

    for(UINT i=0; i<nRuns; ++i) 
    { 
     time_t _1 = time(NULL); 
     std::set<UINT> cRandomSet; 
     cRandomSet.insert(1); 
     cRandomSet.insert(2); 
     cRandomSet.insert(3); 
     cRandomSet.insert(4); 
     g_nElapsed += (time(NULL) - _1); 
    } 


    return 0; 
} 

現在,如果我運行8個線程,每個迭代100000,g_nElapsed將大致40秒。 如果我用800,000次迭代運行1個線程,g_nElapsed約爲5秒。 我的印象是g_nElapsed對於任何合理數量的線程應該大致相同。 可以這麼說......即使工作保持不變,處理器使用率也會隨着線程數量的增加而增加。 但是,似乎與該集合的某種資源爭用導致運行時增加。 但是爲什麼?這是線程本地...

我敢肯定這是一個簡單的誤解和一個簡單的修復,但我不太確定問題在這裏。

下面的代碼不會出現此行爲:

extern time_t g_nElapsed; 
UINT Thread(LPVOID _param) 
{ 
    UINT nRuns = (UINT)_param; 

    for(UINT i=0; i<nRuns; ++i) 
    { 
     time_t _1 = time(NULL); 
     UINT n[4]; 
    n[0] = 1; 
     n[1] = 1; 
     n[2] = 1; 
     n[3] = 1; 
     g_nElapsed += (time(NULL) - _1); 
    } 


    return 0; 
} 
+1

它的分配器鎖定在創建/銷燬對象'cRandomSet'上。另外,每個'cRandomSet.insert'都可以調用** new **,所以它的鎖也是。 – PSIAlt 2013-02-26 12:49:45

+0

內存分配的(不那麼)美麗的一面...... – 2013-02-26 12:58:46

回答

3

你正在創建和銷燬許多容器,每一個使用operator new分配內存。在許多系統上,這需要同步來管理通常的小型分配(如您的)所分配的空閒內存。所以你可能會在那裏引發很多的線程間爭用。

您可以嘗試不同的分配器,如tcmalloc(http://goog-perftools.sourceforge.net/doc/tcmalloc.html)。它是專門爲解決這個問題而設計的。

另一種方法是使用對象池或其他分配策略來避免完全使用標準分配機制。這將需要一些代碼更改,而使用tcmalloc則不需要。

+1

分配器的對象池實現只需要很少的代碼更改,而不需要編寫分配器ofc。 – 2013-02-26 12:58:08

+1

當然,但很少有比沒有更多!我並不是說不使用對象池或其他類型,他們也是一個好主意。 – 2013-02-26 12:59:29

+0

你說得對。使用UINT * nnn = new UINT [5]實現第二​​個變體,並刪除與地圖和向量具有相同行爲的結果。我無法相信我從來沒有把新/刪除視爲爭奪資源。我將檢查tcmalloc庫。 – namezero 2013-02-26 13:19:22