我正在研究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
。
答案 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 :(得分:1)
我的本地标准草案在{30}中提到了lock
效果:所有参数都通过对每个参数的lock(),try_lock()或unlock()的一系列调用来锁定 论点。调用序列不应导致死锁,否则不指定。 [注:A 必须使用诸如try-and-back-off之类的死锁避免算法,但具体算法不是 指定以避免过度约束实现。 - 结束注释]如果调用lock()或try_lock() 抛出异常,应该为任何被调用锁定的参数调用unlock() lock()或try_lock()。
所以,很明显它可能会释放在工作时获得的锁,但它没有说明在进入之前锁定时是否已经释放锁定。
据推测,只要
它不应该使内部发生的事情有所不同。请注意,语言“......每个参数上的一系列调用”当然似乎允许在输入之前锁定某些内容时调用unlock
。
答案 2 :(得分:0)
我想你问你关于互斥锁上的第二个std :: lock之前已经锁定在同一个线程中的问题。如果已经锁定的资源是recursive_mutex,则允许它。如果它是一般的互斥锁,则会陷入僵局。