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

C++ 对std::lock的调用是否可以传递已被调用线程锁定的资源?,c++,multithreading,C++,Multithreading,我正在研究该函数的可能实现,无意中发现了 引用公认的答案强调: 不,这不符合std::lock的定义 它std::lock保证无论您指定的顺序是什么 在参数列表中锁定不会陷入死锁 情况 [……] 这也意味着,如果列表中的锁已被锁定,则它必须 释放,以便以正确的顺序获取锁 我无法找到最后一句话是否正确的结论性答案 我的问题:是否允许(即定义的行为)将调用线程拥有的锁定资源作为标准std::lock函数的参数传递 std::mutex m1, m2; m1.lock(); std::lock(m1,

我正在研究该函数的可能实现,无意中发现了

引用公认的答案强调:

不,这不符合std::lock的定义

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

[……]

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

我无法找到最后一句话是否正确的结论性答案

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

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

我的直觉告诉我这是不允许的。函数需要两个或多个对象,无法检查当前执行线程是否已锁定可锁定对象。因此,用这种方式实现std::lock似乎是不可能的。

我想你会问你的问题,关于互斥体上线程中的第二个std::lock,它之前已经锁定在同一个线程中。如果已锁定的资源是递归\u互斥体,则允许使用该资源。如果是通用互斥锁,则会出现死锁。

我的本地标准草案在30.4.3/5中提到了锁

效果:所有参数都是通过一系列对每个参数的lock、try_lock或unlock调用锁定的 论点调用顺序不应导致死锁,但在其他方面未指定。[注:A] 必须使用诸如try和backoff之类的死锁避免算法,但不使用特定的算法 指定以避免过度约束实现。-结束说明]如果调用lock或try\u lock 抛出异常,则应为调用锁定的任何参数调用unlock 锁定或尝试锁定

所以,很明显,它可能会释放在工作时获得的锁,但它并没有说明在进入之前持有的锁是否在退出时被释放

想必,只要其中一个

成功且所有可锁定项均已锁定,或 它抛出,而那些以前被这个线程锁定的可锁定项现在仍然没有以前未持有的锁,也没有以前锁定的项现在被释放 里面发生的事情应该不会有什么区别。请注意,语言。。。一连串的呼叫。。。在每个参数上,似乎都允许在进入前对锁定的对象调用unlock

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

不,不是。我刚刚测试了一下,结果出现了僵局。必须释放传递给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::锁定:< /P> 如果其中一个互斥类型不是递归互斥,并且当前线程已经拥有m中的相应参数,则该行为是未定义的

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

参考资料:


你的回答清楚地告诉我,我必须更详细地回答我的问题。但您推断出了我想问的问题:场景实际上是锁定互斥体,然后将其作为参数传递给std::lock。我已经更新了我的问题,并添加了一个示例片段。您引用了标准中关于std::lock的部分,其中提到了std::try_lock。为什么不继续阅读关于std::try_lock的标准?标准上说std::try_lock试图锁定互斥锁,但没有阻塞。最后,很抱歉,我无法在移动web版本中输入新行:-如果互斥锁当前被调用此函数的同一线程锁定,它将产生具有未定义行为的死锁。有关允许来自同一线程的多个锁的互斥类型,请参见recursive_mutex。因此,必须在任何可锁定对象上工作的通用实现必须假定调用线程尚未锁定任何一个对象。我可以传递一个互斥体,它是一个可锁定的对象,试图锁定当前线程持有的互斥体是未定义的行为。但是可以为公开其状态的可锁定对象提供专门化,例如std::unique_lock,它具有owns_lock方法,可以利用这些额外信息。我想这并不奇怪!