2012-07-25 51 views
6

我相信我已經很好地掌握了C++中多線程的基礎知識,但是我一直無法得到關於在共享資源周圍鎖定互斥體的明確答案在構造函數或析構函數中。我的印象是你應該鎖定在兩個地方,但最近同事不同意。在構造函數和析構函數中鎖定共享資源

class TestClass 
{ 
public: 

    TestClass(const float input) : 
     mMutex(), 
     mValueOne(1), 
     mValueTwo("Text") 
    { 
     //**Does the mutex need to be locked here? 
     mValueTwo.Set(input); 
     mValueOne = mValueTwo.Get(); 
    } 

    ~TestClass() 
    { 
    //Lock Here? 
    } 

    int GetValueOne() const 
    { 
     Lock(mMutex); 
     return mValueOne; 
    } 

    void SetValueOne(const int value) 
    { 
     Lock(mMutex); 
     mValueOne = value; 
    } 

    CustomType GetValueTwo() const 
    { 
     Lock(mMutex); 
     return mValueOne; 
    } 

    void SetValueTwo(const CustomType type) 
    { 
     Lock(mMutex); 
     mValueTwo = type; 
    } 

private: 

    Mutex mMutex; 
    int mValueOne; 
    CustomType mValueTwo; 
}; 

當然一切都應該是通過初始化列表安全的,但對於構​​造函數中的語句:假裝以下類是由多個線程訪問?在析構函數中,執行非範圍鎖定會有好處,並且永遠不會解鎖(本質上只是調用pthread_mutex_destroy)?

+3

當你說的「類」是在多個線程之間使用,我假設你的意思是TestClass類型的對象可能在多個線程中使用。在這種情況下,你仍然只創建一個對象,所以你不需要在構造函數中鎖定。如果兩個線程同時在構造函數中,則它們將創建2個獨立的對象。鎖定對象構造更合理,以確保(例如)在對象完成構建之前不使用mValueTwo。析構函數似乎應該鎖定,以確保數據在被銷燬時不被訪問。 – Rollie 2012-07-25 15:09:45

+0

@Rollie是的,我確實認爲這個對象是共享的。所以,如果我創建了: – Brett 2012-07-25 15:17:32

+1

@Rollie:在破壞的時候訪問類是一個帶有實例生命週期管理的錯誤 - 當發生這種情況時程序已經被破壞(如果它可以在破壞時被訪問,它也可能在事後發生)。 – 2012-07-25 15:18:18

回答

12

多個線程不能構造同一個對象,也不應該允許任何線程在完全構造之前使用該對象。所以,在合理的代碼中,沒有鎖定的建築是安全的。

銷燬是一個稍微困難的情況。但是,對象的正確生命週期管理可以確保在有可能某些線程仍然可以使用它時,對象不會被銷燬。

一個共享指針可以幫助實現這個例如。 :

  • 構建體在某些線程對象
  • 通共享指針到每個需要的對象訪問的線程(包括構造它,如果需要的線程)
  • 對象將被破壞時的所有主題已經發布了共享指針

但顯然,其他有效的方法存在。關鍵是要在物體一生的三個主要階段之間保持適當的界限:建築,使用和銷燬。切勿在這些階段之間進行重疊。

+1

使用shared_ptr通常是不夠的。這是因爲放棄shared_ptr的最後一個線程將運行析構函數,但該線程可能不是修改對象的最後一個線程。如果對象包含複雜數據成員(如矢量或映射),並且析構函數不獲取並釋放鎖,則析構函數可能會看到過時的內存並導致崩潰。 – 2014-12-14 23:59:27

+0

@MichiHenning:線程只能在完成對象的所有操作之後釋放它的共享指針,這是一個非常合理的(如果不是明顯的)要求。所以,鎖定是不必要的。陳舊的記憶的確在理論上是可能的,但要處理這個問題,你需要一個記憶障礙,而不是鎖定。 (當然,一些流行的線程庫包含一些鎖定操作的屏障,所以也許這就是你的意思) – 2014-12-15 20:22:40

+0

這裏是場景。在線程A中創建shared_ptr 並將shared_ptr(正確聯鎖)傳遞給線程B.線程A更新foo的狀態並刪除其指針。後來,線程B放棄其shared_ptr,這導致線程B調用foo的析構函數。如果線程B自從線程A上次更新foo以來沒有越過內存屏障,則foo的析構函數將對陳舊的數據進行操作。如果foo包含可能導致崩潰的複雜數據成員(如地圖)。你說得對,需要記憶障礙。恰巧互斥體也造成了障礙。 – 2014-12-16 00:25:44

1

它們不必被鎖定在構造函數中,因爲外部任何人都可以訪問該數據的唯一方法就是如果從構造函數本身傳遞它們(或者執行一些未定義的行爲,如調用一個虛擬方法)。

[編輯:析構函數左右去除部,因爲作爲一個評論理所當然斷言,你有更大的問題,如果你從一個對象,這可能是死者試圖訪問資源]

+1

如果另一個線程可能在對象被銷燬時訪問該對象,則會出現錯誤,並且鎖定將無濟於事(請考慮如果銷燬線程首先獲取互斥鎖,會發生什麼情況)。另外,我沒有看到在'shared_ptr'中包含互斥體將會實現什麼。 – interjay 2012-07-25 15:20:06

+0

你是對的,我同意你有一個問題,如果你有機會從一個即將死亡的對象訪問數據。 shared_ptr提議是爲了避免互斥體與對象一起被銷燬的情況,但我想你必須在訪問該對象中的任何內容之前鎖定該互斥體,因此如果它位於對象內部,則不起作用。我會編輯我的答案。 – 2012-07-25 15:47:55

相關問題