C ++ Boost线程。如何使用递归尝试锁?死锁发生在并发代码中

时间:2011-11-10 22:50:35

标签: c++ boost thread-safety deadlock

我正在使用boost在C ++中开发线程安全的懒惰对象模式。但是,在这样做的时候,如果我的应用程序有多个线程,我会在LazyObject的{​​{1}}方法中陷入死锁状态。

这与calculate()有某种关系,因为一旦我用常规互斥锁屏蔽代码并让其他线程等待这个互斥锁,一切都很顺利。然而,阻止其他线程的缺点是它们实际上都需要耗费时间boost::recursive_mutex::scoped_try_lock,因为一个线程经常将performCalculations()标志更改为calculated_。另请注意,false纯粹是虚拟的,派生的实例将以递归方式调用performCalculations()。我想用互斥锁来屏蔽这种无限递归。

你能看到我在哪里出错吗?

我的LazyObject::calculate()具有以下属性:

LazyObject

3 个答案:

答案 0 :(得分:2)

您提供的一项功能看起来很合理。

但是,我强烈怀疑你有锁定订购问题。一个类中有5个互斥锁。您需要保证这些互斥锁始终以相同的顺序锁定。否则你就会死锁。

看起来你有一个非常复杂的锁定顺序:

  • 5种不同的互斥体
  • 1是递归的
  • 至少在尝试锁定

答案 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();
}