2013-10-08 41 views
2

假設我有一個原子指針:延遲初始化

std::atomic<void*> mItems; 

,並在功能,當一個線程需要訪問,它首先檢查它,如果它爲null,線程將分配內存它:

void* lItems = std::atomic_load_explicit(&mItems, memory_order_relaxed); 
if(lItems == nullptr) 
{ 
    void* lAllocation = malloc(...); 

    if(!std::atomic_compare_exchange_strong_explicit(
     &mItems, 
     &lItems, 
     lAllocation, 
     memory_order_relaxed, 
     memory_order_relaxed)) 
    { 
     free(lAllocation); 
    } 
} 
    ... 

但如果N線程運行此方法併發看到mItems等於空,然後所有的人都將分配內存和N - 1它們將釋放agian。

我如何用更好的方法編寫類似的方法。

+1

Punch [「double checked locking」](http://en.wikipedia。org/wiki/Double-checked_locking)到您最喜歡的搜索引擎中。 –

+4

驚喜!這是一個解決的問題http://en.cppreference.com/w/cpp/thread/call_once –

+1

@ R.MartinhoFernandes:但我懷疑'call_once'是無鎖的。 –

回答

1

我想你可以使指針成爲你的互斥量,使用一些衆所周知的值(比如全局地址)作爲其他線程已經在分配的標誌。因此,你的值是:NULL - >魔術「分配進行中」指針 - >實際分配。

的代碼會做這樣的事情:

  • 加載地址:它會具有以下值之一:
    1. NULL:CAS魔法值
      • 沒有CAS成功嗎?如果是的話,我們正在做分配,每個人都知道它
        • 做的分配,存儲新地址,我們完成(不應該有CAS它,因爲我們已經保證排除與第一個CAS)
      • 沒有,那麼別人在做分配,回到1個
    2. 地址不爲空,但魔法值
      • 所以有人已經在做分配 - 只是等待,直到它的變化,並使用最終值
    3. 既不是NULL也不神奇,所以它已經是一個真正的分配值 - 只是用它

這樣,只有一個線程執行的分配,但你的其他N-1個線程可能是忙等待。這是否真的更好會有所不同...

+0

謝謝,這種方法也在我腦海裏。但是這與once_flag類似。我只搜索更好的方法,儘管所有這些都是可以接受的,但是爲你的答案+1。 – MRB

+0

區別在於你沒有單獨的標誌:你做一個單一的負載,只有在角落的情況下,CAS是一個單詞。在通常的快速情況下,您不會發生緩存或其他開銷。 – Useless

+0

我從無鎖碼 – MRB

0

當我得到它,只要第一個線程執行你的函數就需要這個結構,那麼在啓動任何線程之前如何移動分配?我的意思是,重新排列代碼,以便函數以原子指針作爲參數被調用,並且在調用函數的任何線程都被生成之前分配結構(如果分配失敗,也可以避免創建任何線程)

類似:

std::atomic<void*> mItems; 
void func_that_uses_mItems(); 

int main() 
{ 
    mItems = init_struct(); 
    std::thread t1 { &func_that_uses_mItems }; 
    std::thread t2 { &func_that_uses_mItems }; 


    // ... join with or detach threads ... 

    return(0); 
} 

如果分配失敗拋出一個異常,並沒有線程啓動。

+0

的角度講,問題是我不想這樣做。假設我有一個項目的數組,每個指向一個其他項目的數組,我只想初始化它們,只有當一些身體訪問它們。 – MRB