1. 程式人生 > >讀Muduo原始碼筆記---1

讀Muduo原始碼筆記---1

  1. 物件銷燬時出現的競態條件:
  • 析構物件時,其他執行緒是否正在執行該物件的成員函式;
  • 在執行成員函式期間,物件不會被其他執行緒析構;
  • 在呼叫成員函式之前,如何確定物件還活著。
  1. 執行緒安全的類:
  • 多執行緒訪問時,變現出正確的行為;
  • 無論作業系統如何排程這些執行緒,以及執行緒的執行順序;
  • 呼叫端程式碼不需要額外的同步。
  1. 簡單的執行緒安全類
class Counter

{

  public:

    Counter():value_(0){}

int value() const;

int getAndIncrease();

 private:

    int value_;

    mutable MutexLock mutex_;

};



int Counter::value() const

{

  MutexLockGuard lock(mutex_);

  return value_;

}

int Counter::getAndIncrease()

{

  MutexLockGuard lock(mutex_);

  int ret=value_++;

  return ret;

}

每個物件都有自己的mutex_,因此不同物件之間不構成鎖爭用。

  1. 存在的問題:銷燬太難,解構函式呼叫的前提是成員變數mutex_被銷燬。無法保證析構的執行緒安全。
  2. C++記憶體問題:
  • 緩衝區溢位;
  • 空懸指標/野指標
  • 重複釋放
  • 記憶體洩漏
  • 不配對的new/delete
  • 記憶體碎片

執行緒同步精要

  1. 執行緒同步四項原則

  • 儘量最低限度地共享物件,減少需要同步的場合。
  • 使用高階併發程式設計構件。
  • 最後不得已必須使用底層原語時,只用非遞迴的互斥器和條件變數,慎用讀寫鎖,不要用訊號量。
  1. 互斥器(mutex)使用原則:
  • 用RAII手法封裝mutex的建立、銷燬、加鎖、解鎖。
  • 不可重入mutex
  • Lock()和unlock()函式的功能交給Guard物件的構造和析構。
  • 不使用跨程序的mutex。
  • 加解鎖在同一個執行緒。
  • 必要時可考慮用PTHREAD_MUTEX_ERRORCHECK來拍錯。
  1. 鎖小結

互斥鎖保護了一個臨界區,在這個臨界區中,一次最多隻能進入一個執行緒。如果有多個程序在同一個臨界區內活動,就有可能產生競態條件(race condition)導致錯誤。

讀寫鎖從廣義的邏輯上講,也可以認為是一種共享版的互斥鎖。如果對一個臨界區大部分是讀操作而只有少量的寫操作,讀寫鎖在一定程度上能夠降低執行緒互斥產生的代價。

條件變數允許執行緒以一種無競爭的方式等待某個條件的發生。當該條件沒有發生時,執行緒會一直處於休眠狀態。當被其它執行緒通知條件已經發生時,執行緒才會被喚醒從而繼續向下執行。

可遞迴鎖與非遞迴鎖:二者唯一的區別是,同一個執行緒可以多次獲取同一個遞迴鎖,不會產生死鎖。而如果一個執行緒多次獲取同一個非遞迴鎖,則會產生死鎖。

  1. MutexLock mutex;  
  2.   
  3. void foo()  
  4. {  
  5.     mutex.lock();  
  6.     // do something  
  7.     mutex.unlock();  
  8. }  
  9.   
  10. void bar()  
  11. {  
  12.     mutex.lock();  
  13.     // do something  
  14.     foo();  
  15.     mutex.unlock();   
  16. }  

如果MutexLock鎖是個非遞迴鎖,則這個程式會立即死鎖。但是這並不意味著應該用遞迴鎖去代替非遞迴鎖。遞迴鎖用起來固然簡單,但往往會隱藏某些程式碼問題。比如呼叫函式和被呼叫函式以為自己拿到了鎖,都在修改同一個物件,這時就很容易出現問題。因此在能使用非遞迴鎖的情況下,應該儘量使用非遞迴鎖,因為死鎖相對來說,更容易通過除錯發現。程式設計如果有問題,應該暴露的越早越好。