讀Muduo原始碼筆記---1
阿新 • • 發佈:2018-11-04
- 物件銷燬時出現的競態條件:
- 析構物件時,其他執行緒是否正在執行該物件的成員函式;
- 在執行成員函式期間,物件不會被其他執行緒析構;
- 在呼叫成員函式之前,如何確定物件還活著。
- 執行緒安全的類:
- 多執行緒訪問時,變現出正確的行為;
- 無論作業系統如何排程這些執行緒,以及執行緒的執行順序;
- 呼叫端程式碼不需要額外的同步。
- 簡單的執行緒安全類
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_,因此不同物件之間不構成鎖爭用。
- 存在的問題:銷燬太難,解構函式呼叫的前提是成員變數mutex_被銷燬。無法保證析構的執行緒安全。
- C++記憶體問題:
- 緩衝區溢位;
- 空懸指標/野指標
- 重複釋放
- 記憶體洩漏
- 不配對的new/delete
- 記憶體碎片
執行緒同步精要
-
執行緒同步四項原則
- 儘量最低限度地共享物件,減少需要同步的場合。
- 使用高階併發程式設計構件。
- 最後不得已必須使用底層原語時,只用非遞迴的互斥器和條件變數,慎用讀寫鎖,不要用訊號量。
- 互斥器(mutex)使用原則:
- 用RAII手法封裝mutex的建立、銷燬、加鎖、解鎖。
- 不可重入mutex
- Lock()和unlock()函式的功能交給Guard物件的構造和析構。
- 不使用跨程序的mutex。
- 加解鎖在同一個執行緒。
- 必要時可考慮用PTHREAD_MUTEX_ERRORCHECK來拍錯。
- 鎖小結
互斥鎖保護了一個臨界區,在這個臨界區中,一次最多隻能進入一個執行緒。如果有多個程序在同一個臨界區內活動,就有可能產生競態條件(race condition)導致錯誤。
讀寫鎖從廣義的邏輯上講,也可以認為是一種共享版的互斥鎖。如果對一個臨界區大部分是讀操作而只有少量的寫操作,讀寫鎖在一定程度上能夠降低執行緒互斥產生的代價。
條件變數允許執行緒以一種無競爭的方式等待某個條件的發生。當該條件沒有發生時,執行緒會一直處於休眠狀態。當被其它執行緒通知條件已經發生時,執行緒才會被喚醒從而繼續向下執行。
可遞迴鎖與非遞迴鎖:二者唯一的區別是,同一個執行緒可以多次獲取同一個遞迴鎖,不會產生死鎖。而如果一個執行緒多次獲取同一個非遞迴鎖,則會產生死鎖。
- MutexLock mutex;
- void foo()
- {
- mutex.lock();
- // do something
- mutex.unlock();
- }
- void bar()
- {
- mutex.lock();
- // do something
- foo();
- mutex.unlock();
- }
如果MutexLock鎖是個非遞迴鎖,則這個程式會立即死鎖。但是這並不意味著應該用遞迴鎖去代替非遞迴鎖。遞迴鎖用起來固然簡單,但往往會隱藏某些程式碼問題。比如呼叫函式和被呼叫函式以為自己拿到了鎖,都在修改同一個物件,這時就很容易出現問題。因此在能使用非遞迴鎖的情況下,應該儘量使用非遞迴鎖,因為死鎖相對來說,更容易通過除錯發現。程式設計如果有問題,應該暴露的越早越好。