2011-10-22 75 views
4

在論文http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm中提出了一種算法,該算法在本地靜態變量的初始化期間不需要保持鎖定,但仍然導致通過變量定義的併發控制流等待直到初始化完成。本地靜態初始化沒有保持鎖定可以避免C++ 11中可能出現死鎖?

本文說,這具有避免可能的死鎖

的核心問題與功能局部靜態持續時間的對象初始化,含功能可以同時調用的優點,並且因此定義可以同時執行。未能同步可能會引入競爭條件。顯而易見的解決方案是同步。問題是,如果同步涉及在初始化器執行時持有鎖,則此類同步可能會導致死鎖。

有人可以請舉一個例子來說明上述死鎖發生在哪裏嗎?

回答

4

如果你打算在本地靜態初始化期間持有的鎖有兩種可能的設計:

  1. 分配每靜態互斥。
  2. 爲所有靜態分配一個互斥量。

我不是100%肯定的,但我相信報價以你提到隱含地假定設計2.的確在本文介紹的算法只使用一個互斥體的所有靜態(代碼名爲mu) 。

在設計2下,您遇到this stack overflow answer所述的死鎖。也就是說,除非你在初始化時沒有真正保存互斥鎖。這是通過爲每個靜態指定一個三態標誌來實現的:未初始化,正在初始化,已經初始化。並使用全局互斥鎖來設置標誌,但在初始化過程中將其解鎖。

+0

啊,這是有道理的。該文件還說,GCC使用那種容易出現死鎖的算法。鏈接的答案證明它。謝謝。 –

+0

這個答案剛剛投了兩次票。任何人都在關心精心製作?或者它只是一個隨機的,無意義的驅動器?我正在提供準確的信息。我相信這個答案是準確的。如果需要闡述,請詢問。或者至少在這裏留下一個評論,並說明反對票的理由。 –

+0

幾個小時前,有人在我的一些答案/問題上有一些downvote的樂趣:http://img40.imageshack.us/img40/5457/snapshot96.png。也許這是同一個用戶?如果系統認爲它是辱罵性的,那麼它就不會計入那些低估。 –

3

這是經典死鎖的簡單擴展,其中一個鎖是編譯器提供的。如果一個線程調用A2B()和另一個線程調用B2A()發生

void A2B() { a.Lock(); B(); a.Unlock(); } 
void B() { b.Lock(); ...; b.Unlock(); } 
void B2A() { b.Lock(); A(); b.Unlock(); } 
void A() { a.Lock(); ...; a.Unlock(); } 

經典的僵局。

在靜態初始化鎖中,編譯器提供了b鎖。

int A() { a.Lock(); ...; a.Unlock(); return 0; } 
void B2A() { static int v = A(); } 
void A2B() { a.Lock(); B2A(); a.Unlock(); } 

如果假設周圍靜態初始化的鎖,那麼代碼被祕密改

void B2A() { 
    if (!initialized) { 
     b.Lock(); // standard double-check-lock 
     if (!initialized) v = A(); 
     initialized=true; 
     b.Unlock(); 
    } 
} 

一個線程調用A2B()和其他呼叫B2A()

+0

這種「經典死鎖」也被稱爲鎖反轉。 –

+0

如果我們在初始化'v'時沒有保持鎖定狀態,如果其他人需要等到'v'被初始化,我們纔會有相同的死鎖嗎?一個線程調用'B2A'並進入初始化。另一個線程調用'A2B'並鎖定'a'並調用'B2A'並且需要等到'v'的初始化完成。現在第一個線程調用'A',並且需要等到'a'被'A2B'解鎖。 –

+0

最初的C++標準通過說「如果你打電話給B2A兩次,並且第一次調用仍然忙於嘗試初始化'v',那麼第二次調用只是繼續一個未初始化的'v''來避免這個問題。 –