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

C++ Boost threads. How to use recursive try lock? Deadlock happens in concurrent code

本文关键字:死锁 并发 代码 线程 Boost 何使用 递归 c++      更新时间:2023-10-16

我正在使用boost在c++中开发一个线程安全的惰性对象模式。然而,当我这样做时,如果我的应用程序有多个线程,我就会在LazyObjectcalculate()方法中进入死锁状态。

这在某种程度上与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);
    }
}

您提供的一个函数看起来不错。

然而,我强烈怀疑你有锁排序问题。在一个类中有5个互斥体。您需要保证这些互斥锁总是以相同的顺序锁定。否则就会死锁。

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

  • 5个不同的互斥体
  • 1是递归的

也许您可以提供您想要实现的目标的描述。您没有提供完整的代码,所以只能猜测。

例如,如果一个线程刚刚将calculated_设置为true,执行了condVariable_.notifyAll(),在解锁waitMutex_之前被抢占,然后另一个线程阻塞在condVariable_.wait(lock),然后没有人来唤醒它,你可以死锁。

我看到你在评论中写了"互斥锁和信号量",注意条件变量没有内存,它不像信号量或Windows事件对象。

给出一个更好的问题描述,我真的不认为上面的代码是可利用的:)

你是对的,上面的函数并没有给出一个清晰的全貌。基本上下面是所有相互作用的函数,它们争夺*this

的资源

我能够将互斥体的数量减少到只使用3个。但我认为这个问题不能实际解决少数量的互斥锁。前提条件是更新方法必须尽可能便宜。

我还有一个关于异常抛出的问题。如您所见,执行performcomputation的计算线程可能会抛出异常。如果有一些线程等待信号继续,它们就不能继续,因为甚至发生了任何异常。是否有可能使用boost以某种方式让唤醒线程抛出与信令线程抛出相同的异常?如果是,你能提供明确的代码如何工作的想法?

我的类需要下列属性。

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