我正在使用boost在C ++中开发线程安全的懒惰对象模式。但是,在这样做的时候,如果我的应用程序有多个线程,我会在LazyObject
的{{1}}方法中陷入死锁状态。
这与calculate()
有某种关系,因为一旦我用常规互斥锁屏蔽代码并让其他线程等待这个互斥锁,一切都很顺利。然而,阻止其他线程的缺点是它们实际上都需要耗费时间boost::recursive_mutex::scoped_try_lock
,因为一个线程经常将performCalculations()
标志更改为calculated_
。另请注意,false
纯粹是虚拟的,派生的实例将以递归方式调用performCalculations()
。我想用互斥锁来屏蔽这种无限递归。
你能看到我在哪里出错吗?
我的LazyObject::calculate()
具有以下属性:
LazyObject
答案 0 :(得分:2)
您提供的一项功能看起来很合理。
但是,我强烈怀疑你有锁定订购问题。一个类中有5个互斥锁。您需要保证这些互斥锁始终以相同的顺序锁定。否则你就会死锁。
看起来你有一个非常复杂的锁定顺序:
答案 1 :(得分:0)
也许您可以提供您想要实现的目标的描述。你没有提供整个代码,所以人们只能猜测。
例如,如果一个线程刚刚将calculated_
设置为true
,执行condVariable_.notifyAll()
并在解锁waitMutex_
之前被抢占,然后另一个线程阻塞在{ {1}}然后没有人来唤醒它。
我在评论中看到你写的“互斥和信号量”,注意条件变量没有内存,它就像信号量或Windows事件对象。
更好地描述问题,我不认为上面的代码是可以挽救的:)
答案 2 :(得分:0)
你是对的,上面的功能并没有清楚地了解整个画面。基本上下面是所有相互作用的函数,它们争夺* this
的资源我能够将互斥量减少到仅使用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();
}