2011-11-10 32 views
2

我正在C++中使用boost開發線程安全的惰性對象模式。然而,在這樣做的時候,如果我的應用程序有多個線程,我會在LazyObjectcalculate()方法中進入死鎖狀態。C++ Boost線程。如何使用遞歸嘗試鎖定?併發代碼中發生死鎖

這與某種程度上與boost::recursive_mutex::scoped_try_lock有關,因爲一旦我屏蔽了常規互斥鎖的代碼,並讓其他線程在此互斥鎖上等待,一切都很順利。然而,阻止其他線程的缺點是,它們實際上都需要耗費時間performCalculations(),因爲一個線程經常將calculated_標誌更改爲false。另請注意,performCalculations()純粹是虛擬的,派生實例將遞歸地調用LazyObject::calculate()。我想用互斥鎖來屏蔽這個無限遞歸。

你能看到我在哪裏出錯嗎?

LazyObject具有以下屬性:

// protects resource frozen_ from simultaneous freeze, unfreeze calls 
mutable boost::mutex frozenMutex_; 
// protects resource calculated_ from simultaneous update, calculate calls 
mutable boost::mutex calculatedMutex_; 

// protects that only one thread can simultaneously call calculate 
mutable boost::recursive_try_mutex waitMutex_; 
// protects that only once can performCalculations be called (from same thread) 
mutable boost::mutex blockingMutex_; 

// mutex and semaphore for sleeping threads until calculate is ready 
mutable boost::mutex condMutex_; 
mutable boost::condition_variable condVariable_; 

inline void LazyObject::calculate() const { 

    boost::recursive_mutex::scoped_try_lock lock(waitMutex_); 
    if (lock) { 
     //recursive lock lets same thread pass, puts others on wait 
     if (!calculated_ && !frozen_ && blockingMutex_.try_lock()) { 
      // blockingMutex ensures that only once same thread 
      // can call performCalculations 
      try { 
       performCalculations(); 
       calculated_ = true; 
       blockingMutex_.unlock(); 
       condVariable_.notify_all(); 
      } catch (...) { 
       calculated_ = false; 
       blockingMutex_.unlock(); 
       condVariable_.notify_all(); 
       throw; 
      } 
     } 
    } else { 
     // start a non blocking wait until calculation is ready 
     boost::mutex::scoped_lock lock(condMutex_); 
     condVariable_.wait(lock); 
    } 
} 

回答

2

您所提供的一個功能看起來聲音。

但是,我強烈懷疑你有鎖訂購問題。你在一個班級有5個互斥體。你需要保證那些互斥鎖是總是被鎖定在相同的順序。否則,你會有一個死鎖。

它看起來就像你有一個非常複雜的鎖順序:

  • 5種不同的互斥量
  • 1是遞歸
  • 至少在試圖鎖定
+0

其實有一個問題。我沒有考慮到這一點。顯而易見的情況是,一旦計算出懶惰的對象,並隨後調用計算,所有調用都應該返回,因爲對象的狀態是最新的。在上面的實現中,一些線程進入睡眠狀態 – Lauri

0

也許你可以提供描述你想達到什麼目的。你沒有提供完整的代碼,所以只能猜測。

例如,如果一個線程剛成立calculated_true,執行condVariable_.notifyAll()和解鎖waitMutex_之前被搶佔,然後在condVariable_.wait(lock)就再也沒有人另一個線程塊終究來喚醒它可以死鎖。

我在你寫的「互斥體和信號量」的評論中看到,注意條件變量沒有內存,它沒有像信號量或Windows事件對象。

給這個問題的一個更好的描述,我真的不認爲上述這段代碼是挽救:)

0

你是正確的,上面的功能沒有給整個畫面的清晰認識。基本上下面是所有相互作用的功能,爭奪*這個資源

我能夠減少互斥量只使用3。先決條件是更新方法必須儘可能便宜。

我還有一個關於異常拋出的問題。正如你所看到的,執行performCalculations的計算線程可以拋出異常。如果有一些線程在等待信號繼續前進,他們就不能繼續,因爲甚至發生了異常。是否有可能使用boost來讓喚醒線程拋出信號線程中拋出的SAME異常。如果是的話,你能否提供明確的代碼這個想法的工作原理?

我的班級需要以下attirbutes。

// state variables indicating is calculation necessary 
mutable bool calculated_, frozen_; 

// flag that tells waking threads to throw exceptions if 
// LazyObject::performCalculations() threw any exceptions 
mutable bool failed_; 
// flag avoiding infinite recursion on single thread not recursively 
// calling LazyObject::performCalculations() through recursive calls 
// to LazyObject::calculate() 
mutable bool calculating_; 

// protects resources from simultaneous read & writes 
mutable boost::mutex readWriteMutex_; 

// protects that only one thread can simultaneously call calculate 
//mutable boost::mutex waitMutex_; 
mutable boost::recursive_try_mutex waitMutex_; 

// mutex and semaphore for sleeping threads until calculate is ready 
mutable boost::mutex condMutex_; 
mutable boost::condition_variable condVariable_; 


inline void LazyObject::performCalculations() { 
    // let derived classes specialize own implementation 
} 

inline void LazyObject::update() { 
    // observers don't expect notifications from frozen objects 
    // LazyObject forwards notifications only once until it has been 
    // recalculated 
    readWriteMutex_.lock(); 
    calculated_ = false; 
    readWriteMutex_.unlock(); 
    if (!frozen_) { 
     notifyObservers(); 
    } 
} 

inline void LazyObject::recalculate() { 
    readWriteMutex_.lock(); 
    bool wasFrozen = frozen_; 
    calculated_ = frozen_ = false; 
    try { 
     readWriteMutex_.unlock(); 
     calculate(); 
    } catch (...) { 
     readWriteMutex_.lock(); 
     frozen_ = wasFrozen; 
     readWriteMutex_.unlock(); 
     notifyObservers(); 
     throw; 
    } 
    readWriteMutex_.lock(); 
    frozen_ = wasFrozen; 
    readWriteMutex_.unlock(); 
    notifyObservers(); 
} 

inline void LazyObject::freeze() { 
    readWriteMutex_.lock(); 
    frozen_ = true; 
    readWriteMutex_.unlock(); 
} 

inline void LazyObject::unfreeze() { 
    readWriteMutex_.lock(); 
    frozen_ = false; 
    readWriteMutex_.unlock(); 
    // send notification, just in case we lost any 
    notifyObservers(); 
} 

inline void LazyObject::calculate() const { 

    //boost::recursive_mutex::scoped_try_lock lock(waitMutex_); 

    readWriteMutex_.lock(); 
    // see a snapshot of object's status 
    if (!calculated_ && !frozen_) { 
     if (waitMutex_.try_lock()) { 
      //recursive lock lets same thread pass, puts others on wait 
      if (calculating_) { 
       readWriteMutex_.unlock(); 
       waitMutex_.unlock(); 
       return; 
      } else { 
       calculating_ = true; 
      } 
      readWriteMutex_.unlock(); 

      try { 
       performCalculations(); 

       readWriteMutex_.lock(); 
       calculating_ = false; 
       failed_ = false; 
       calculated_ = true; 
       readWriteMutex_.unlock(); 
       waitMutex_.unlock(); 
       condVariable_.notify_all(); 
       return; 
      } catch (...) { 
       readWriteMutex_.lock(); 
       calculating_ = false; 
       failed_ = true; 
       calculated_ = false; 
       readWriteMutex_.unlock(); 
       waitMutex_.unlock(); 
       condVariable_.notify_all(); 
       throw; 
      } 
     } else { 
      // start a non blocking wait until calculation is ready 
      readWriteMutex_.unlock(); 
      boost::mutex::scoped_lock lock(condMutex_); 
      condVariable_.wait(lock); 
      if (failed_) 
       throw std::exception(); 
      else 
       return; 
     } 
    } 
    // no need to calculate 
    readWriteMutex_.unlock(); 
}