分层互斥锁:可能出现虚假异常吗?

6
在他的书《C++ Concurrency in Action》中,A. Williams介绍了锁层次结构的概念作为避免死锁的机制。以下是一个HierarchicalMutex实现(摘自该书)的简化版本:
class HierarchicalMutex {
   private:
    std::mutex Mutex_;
    unsigned const Level_;
    unsigned PrevLevel_{0};
    static thread_local unsigned current_level;

   public:
    explicit HierarchicalMutex(unsigned level) : Level_{level} {}

    void lock() {
        if (current_level <= this->Level_) { // (1)
            // I can only lock a mutex with a lower level than the currently
            // locked one.
            throw std::logic_error("lock: Out of order");
        }

        this->Mutex_.lock();
        this->PrevLevel_ = std::exchange(current_level, this->Level_);
    }

    // try_lock implemented accordingly [...]

    void unlock() {
        if (current_level != this->Level_)
            throw std::logic_error("unlock: Out of order");

        current_level = this->PrevLevel_;
        this->Mutex_.unlock();
    }
};

// current_level initialized to UINT_MAX so that, in the beginning, any
// HierarchicalMutex may be locked.
thread_local unsigned HierarchicalMutex::current_level{
    std::numeric_limits<unsigned>::max()};

假设有两个线程AB竞争锁定一个HierarchicalMutex实例,如下代码所示:

int main() {
    HierarchicalMutex mutex{1};

    std::thread threadA{[&mutex] { std::scoped_lock sl{mutex}; }};
    std::thread threadB{[&mutex] { std::scoped_lock sl{mutex}; }};

    threadB.join();
    threadA.join();
}

假设有线程A:

  • 调用mutex.lock();
  • 成功地将检查(1)评估为false;
  • 锁定HierarchicalMutex::Mutex_;
  • 更新HierarchicalMutex::current_level并将其设置为1

此时,线程B

  • 调用mutex.lock();
  • 将检查(1)评估为true

这意味着线程B将抛出异常;但我希望它等待直到线程A解锁mutex

我的问题是:

  1. 我描绘的执行流程是否可能?
  2. 如果是的话,线程B抛出异常是否正确,还是应该等待线程A解锁mutex(如我所期望的)?
  3. 如果我的期望是正确的,应该如何实现HierarchicalMutex,以便线程B等待而不是抛出异常?仅仅将 <=替换为<就足够了吗?

在我30年的C++并发编程经验中,我从未见过(或需要)如此复杂的同步原语。它试图解决什么问题?对我来说,它看起来像是一个正在寻找问题的解决方案。 - Pepijn Kramer
@PepijnKramer 这是一种强制要求多个互斥锁始终按照相同的顺序进行锁定和解锁的方法。如果不是这种情况,您将会收到一个异常并修复违反锁定顺序的代码。在我的示例中,我只使用了一个互斥锁,因为它足以解释我的问题所在。然而,您通常会有几个互斥锁;每个都有自己的级别。 - paolo
好的,我现在明白它应该做什么了。但我仍然想不到任何需要使用它的地方。它似乎意味着互斥锁可以跨越多个函数调用的长度,在其中其他锁也可能发生。很难确切地说出原因,但有些事情让我感到不安。通常我会设计短暂的锁,并且永远不会锁定超过一个作用域。可能是因为我从未需要过这样的东西,所以看起来很奇怪;) - Pepijn Kramer
1个回答

3

此时,线程 B:

调用 mutex.lock() 方法;

计算 check(1) 的结果为 true。

并不是这样的。变量 current_level 被声明为 thread_local 对象。如果您对此不熟悉,请参阅 C++ 教材以获取更完整的讨论,但归结起来就是 current_level 在每个执行线程中都是一个单独的、离散的对象。在两个执行线程中,它们都是 0,并且 check(1) 的值为 false


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接