1. 程式人生 > >muduo之當解構函式遇見執行緒安全

muduo之當解構函式遇見執行緒安全

一、當解構函式遇到多執行緒

當一個物件能被多個執行緒同時看到時,那麼物件的銷燬時機就會變得模糊不清,可能出現多種競態條件:
① 在即將析構一個物件時,如何知道此時是否有別的執行緒正在執行該物件的成員函式?
② 如何保證在執行成員函式期間,物件不會被另一個執行緒析構?
③ 在呼叫某個物件的成員函式之前,如何得知該物件還活著?它的解構函式會不會碰巧執行到一半?

二、物件的銷燬太難

2.1 作為資料成員的mutex不能保護析構

mutex只能保證函式一個接著一個地執行。下面從程式碼開始分析:
在這裡插入圖片描述
儘管執行緒A在銷燬物件x之後把x設為NULL,儘管執行緒B在呼叫x之前檢查了指標x地值,但是還是無法避免一種race condition:
1.執行緒A呼叫delete x,執行到(1)free internal state,執行緒A此刻已經持有了mutex_,即將繼續向下執行。
2.x並沒有被執行緒A設為NULL,因此執行緒B能通過if(x),進入x->update(),將阻塞在(2)處
3.之後因為解構函式銷燬x地同時也將成員變數mutex_給銷燬,因此(2)將會發生難以預料的結果(可能會永遠阻塞在(2)處,也可能會進入“臨界區”,然後core dump,或者發生更糟糕的情況)
結論:作為class資料成員的mutex_只能同步本class的其他資料成員的讀和寫,不能保護安全的析構。

2.2 執行緒不安全的Observer模式

class Subject
{
public:
  virtual ~Subject() {};
  void attach(Observer* obsvr) 
  {
    m_observers.push_back(obsvr);
  }
  void remove(Observer* obsvr)
  {
    m_observers.remove(obsvr);
  }
  void notify(const std::string &msg)
  {
    for(Observer* obs:m_observers)
obs->Update(); [1] } private: std::vector<Observer* > m_observers; //觀察者集合 };

執行緒A:當Subject通知每一個Observer時(程式碼[1]),它無法得知Observer物件是否還活著?
執行緒B:觀察者Observer正在呼叫解構函式
此時執行緒A中,該Observer物件處於將死未死的狀態,呼叫Update()函式,core dump恐怕是最幸運的結果。

三、一個萬能的解決方案

上文講解了各種各樣的錯誤,解決方案的核心要點是:析構過程不需要保護,所有人都用不到的東西一定是垃圾,垃圾自動回收的原理是“引用計數為0時,該物件就是垃圾,需要銷燬”

,引出了帶引用計數的智慧指標。

舉例:下面使用weak_ptr / share_ptr + Mutex實現執行緒安全的Observer模式
既然通過weak_ptr的expired函式可以探查物件的生死,那麼Observer的競態問題就很容易解決,只要讓Observer儲存weak_ptr<Observer>即可。

class Subject
{
public:
  virtual ~Subject() {};
  void attach(weak_ptr<Observer> obsvr);
  // void remove(weak_ptr<Observer> obsvr); //不需要它,會自動移除
  void notify(const std::string &msg)
  {
    //加鎖,任何一個時刻,保證只有一個執行緒去喚醒所有Observer
    MutexLockGuard lock(mutex_); 
    std::vector<weak_ptr<Observer>>::iterator iter = m_observers.begin();
    for(iter != m_observers.end())
    {
      //嘗試提升,即獲得weak_ptr對應的shared_ptr
      shared_ptr<Observer> obj(iter->lock()); 
      if(obj) //成功,返回一個非空的shared_ptr
      {
        //提升成功,現在的引用計數至少為2(想想為什麼?)
        obj->Opdate(); //沒有競態條件
        ++iter;
      }
      else //失敗,返回一個空的shared_ptr
      {
        //物件已經銷燬,從容器中拿掉weak_ptr
        it = m_observers.erase(iter);
      }     
    }
  }
private: 
  mutable MutexLock mutex_;
  std::vector<weak_ptr<Observer>> m_observers; //觀察者集合
};

分析:在呼叫Update()之前,先用it->lock()判斷該物件是否存在,如果存在就呼叫Update();如果不存在,就刪除it。
總的說就是,使用weak_ptr可以探查物件的生死。以便控制物件的生命週期。