是否可以调用std :: lock传递已被调用线程锁定的资源?

时间:2017-12-04 16:29:58

标签: c++ multithreading

我正在研究std::lock函数的可能实现,偶然发现了an implementation posted on the code review community

引用接受的答案(强调我的):

  

这不是不符合std :: lock()的定义

     

它(std :: lock)保证无论你指定什么顺序   在参数列表中锁定您不会陷入死锁   情况。

     

[...]

     

这也意味着如果列表中的锁已被锁定,则必须   被释放,以便以正确的顺序获取锁。

我无法找到最后陈述是否正确的结论性答案。

我的问题:是否允许(即定义的行为)将调用线程拥有的锁定资源作为标准std::lock函数的参数传递?

std::mutex m1, m2;
m1.lock();
std::lock(m1, m2);

我的直觉是说这实际上是不允许的。该函数需要两个或更多Lockable个对象,并且无法检查当前执行线程是否已锁定Lockable对象。所以似乎不可能以这种方式实现std::lock

3 个答案:

答案 0 :(得分:2)

<块引用>

是否允许(即定义的行为)传递锁定的资源?

没有。它不是。我刚刚测试了这个,我陷入了僵局。传递给 std::lock 的两个互斥体都必须被释放。如果它们是递归互斥锁,则它们可能已经被当前线程锁定。否则,如果它们被不同的线程锁定,则会出现死锁。

如果您知道互斥锁何时会被锁定,您可以使用自定义的可锁定对象来只锁定一个互斥锁,例如:

class CustomDualLock {
    bool first_time;
    std::mutex& _mutex1;
    std::mutex& _mutex2;

public:
    CustomDualLock(std::mutex& mutex1, std::mutex& mutex2)
        : first_time(true),
        _mutex1(mutex1), 
        _mutex2(mutex2) {
        lock();
    }

    ~CustomDualLock() {
        unlock();
    }

    CustomDualLock(const CustomDualLock&) = delete;
    CustomDualLock& operator =(const CustomDualLock&) = delete;
    CustomDualLock(const CustomDualLock&&) = delete;
    CustomDualLock& operator =(const CustomDualLock&&) = delete;

    void lock() {
        if( first_time ) {
            first_time = false;
            _mutex1.lock();
        } 
        else {
            std::lock(_mutex1, _mutex2);
        }
    }

    void unlock() {
        _mutex1.unlock(); 
        _mutex2.unlock();
    }
};

更新

我刚刚发现了一些更清楚的行为(它讨论的是作用域锁 (C++ 17),当传递超过 1 个锁时调用 std::lock):https://en.cppreference.com/w/cpp/thread/scoped_lock/scoped_lock

<块引用>

如果 MutexTypes 之一不是递归互斥体并且当前线程已经拥有 m 中的相应参数,则行为未定义...

正如它所说,行为是未定义的。至少在我的编译器(GCC 8.4)上,我遇到了死锁。但可能在其他一些编译器中,我可能没有。

参考文献:

  1. Using more than one mutex with a conditional variable
  2. Condition variable waiting on multiple mutexes
  3. What is the best way to wait on multiple condition variables in C++11?
  4. https://en.cppreference.com/w/cpp/thread/lock
  5. https://en.cppreference.com/w/cpp/thread/condition_variable_any

答案 1 :(得分:1)

我的本​​地标准草案在{30}中提到了lock

  

效果:所有参数都通过对每个参数的lock(),try_lock()或unlock()的一系列调用来锁定   论点。调用序列不应导致死锁,否则不指定。 [注:A   必须使用诸如try-and-back-off之类的死锁避免算法,但具体算法不是   指定以避免过度约束实现。 - 结束注释]如果调用lock()或try_lock()   抛出异常,应该为任何被调用锁定的参数调用unlock()   lock()或try_lock()。

所以,很明显它可能会释放在工作时获得的锁,但它没有说明在进入之前锁定时是否已经释放锁定。

据推测,只要

  1. 它成功并锁定了所有可锁定或
  2. 它会抛出,而且之前被这个线程锁定的那些可锁定区域仍然是(以前没有保持锁定,现在没有锁定以前锁定的项目)
  3. 不应该使内部发生的事情有所不同。请注意,语言“......每个参数上的一系列调用”当然似乎允许在输入之前锁定某些内容时调用unlock

答案 2 :(得分:0)

我想你问你关于互斥锁上的第二个std :: lock之前已经锁定在同一个线程中的问题。如果已经锁定的资源是recursive_mutex,则允许它。如果它是一般的互斥锁,则会陷入僵局。