![](/img/trans.png)
[英]what does unique_lock mean when a single thread acquire 2 unique_lock of the same mutex?
[英]unique_lock same mutex in different thread
我正在看这段代码:
#include <chrono>
#include <iostream>
#include <map>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <thread>
bool flag;
std::mutex m;
void wait_for_flag() {
// std::cout << &m << std::endl;
// return;
std::unique_lock<std::mutex> lk(m);
while (!flag) {
lk.unlock();
std::cout << "unlocked....." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "sleeping....." << std::endl;
lk.lock();
std::cout << "locked by " << std::this_thread::get_id() << "....."
<< std::endl;
}
}
int main(int argc, char const *argv[]) {
std::thread t(wait_for_flag);
std::thread t2(wait_for_flag);
std::thread t3(wait_for_flag);
std::thread t4(wait_for_flag);
std::thread t5(wait_for_flag);
t.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
我是新手,我认为互斥锁只能由一个线程获取。 我有两个问题:
谢谢
因为在获得互斥锁上的锁之后,每个线程都会调用lk.unlock();
现在其他线程可以获取互斥锁上的锁。 只有当一个线程试图锁定一个已经锁定的互斥锁(由不同的线程)时,它才必须等待互斥锁空闲。 因为代码中的任何线程最终都会调用lk.unlock();
不同的线程总是有机会获得互斥锁上的锁并且没有死锁。
例如,如果您有两个互斥体并且两个线程尝试以不同的顺序锁定它们,则会发生死锁:
// thread A
std::unique_lock<std::mutex> lk1(mutex1);
std::unique_lock<std::mutex> lk2(mutex2); // X
// thread B
std::unique_lock<std::mutex> lk2(mutex2);
std::unique_lock<std::mutex> lk1(mutex1); // X
这里可能发生线程 A 锁定 mutex1,线程 B 锁定 mutex2,然后都在X
中等待另一个线程释放另一个 mutex,但这永远不会发生。 这是一个僵局。
2.
锁只是一种纤细的 RAII 类型。 它的唯一目的是在创建时调用互斥lock
的锁,并在销毁时unlock
。 您可以通过手动锁定/解锁互斥锁来编写没有锁的相同代码,但是当互斥锁被锁定时出现异常时,它将永远不会被解锁。
@SolomonSlow 我的问题是,如果我们使用 unique_lock 将互斥锁包装在不同的线程中,为什么没有死锁......?
“死锁”意味着有一些线程集,其中没有一个线程可以继续,直到该组的其他成员之一执行某些操作。 在最简单的死锁中,只有两个线程,并且有两个互斥锁:
unique_lock
,它被阻塞,等待在 mutex 2 上放置锁。在线程 B 先做某事之前,线程 A 不能做任何事情,而在线程 A 先做某事之前,线程 B 也不能做任何事情。 两个线程都无法再做任何事情。 僵局。
如果没有线程等待的至少两个不同的事物(例如,两个不同的互斥体),您就不会出现死锁。 如果只有一个互斥锁,那么无论哪个线程将其锁定,该线程都将能够继续。 当没有线程能够继续时,这只是一个死锁。
在您的示例中,五个线程中的每一个都进入一个循环:
每当您的一个线程锁定互斥锁时,没有什么可以阻止它打印然后返回顶部并再次解锁互斥锁,以便其他线程可以运行。 没有僵局。
这不是一个答案。 这只是一个插图。 我把你的一个例子变成了三个不同的例子,它们都达到了相同的结果。 我希望它可以帮助您更好地理解unique_lock
的作用。
第一种方法根本不使用unique_lock
。 它只使用mutex
。 这是老派的方式——在RAII被发现之前我们做事的方式。
std::mutex m;
{
...
while (...) {
do_work_outside_critical_section();
m.lock(); // explicitly put a "lock" on the mutex.
do_work_inside_critical_section();
m.unlock(); // explicitly remove the "lock."
}
}
老式的方法是有风险的,因为如果do_work_inside_critical_section()
抛出异常,它会将互斥锁留在锁定的 state 中,任何尝试再次锁定它的线程都可能永远挂起。
第二种方式使用unique_lock
,这是RAII的一个体现。
RAII 模式确保没有办法离开这个在互斥锁m
上留下锁的代码块。 unique_lock
析构函数总是会被调用,无论如何,析构函数会移除锁。
std::mutex m;
{
...
while (...) {
do_work_outside_critical_section();
std::unique_lock<std::mutex> lk(m); // constructor puts a "lock" on the mutex.
do_work_inside_critical_section();
} // destructor implicitly removes the "lock."
}
请注意,在此版本中,每次循环都会构造和销毁unique_lock
。 这听起来可能很昂贵,但实际上并非如此。 unique_lock
旨在以这种方式使用。
最后一种方法是您在示例中所做的。 它只创建和销毁一次unique_lock
,但随后它会在循环内重复锁定和解锁它。 这可行,但它的代码行比上面的版本多,这使得阅读和理解有点困难。
std::mutex m;
{
...
std::unique_lock<std::mutex> lk(m); // constructor puts a "lock" on the mutex.
while (...) {
lk.unlock(); // explicitly remove the "lock" from the mutex.
do_work_outside_critical_section();
lk.lock(); // explicitly put a "lock" back on the mutex.
do_work_inside_critical_section();
}
} // destructor implicitly removes the "lock."
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.