在阅读 c++
std::lock
时,我从 cppreference 中遇到了以下示例:
void assign_lunch_partner(Employee &e1, Employee &e2)
{
static std::mutex io_mutex;
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
}
// use std::lock to acquire two locks without worrying about
// other calls to assign_lunch_partner deadlocking us
{
std::lock(e1.m, e2.m);
std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// Equivalent code (if unique_locks are needed, e.g. for condition variables)
// std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
// std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
// std::lock(lk1, lk2);
// Superior solution available in C++17
// std::scoped_lock lk(e1.m, e2.m);
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
}
e1.lunch_partners.push_back(e2.id);
e2.lunch_partners.push_back(e1.id);
}
send_mail(e1, e2);
send_mail(e2, e1);
}
虽然我确实理解需要将
io_mutex
制作为 static
,以便在对 assign_lunch_partner
函数的并发调用之间共享其状态(如果我错了,请纠正我),但我不明白以下内容:
lk
对象 (lock_guard
) 被限定范围?是因为天生的原因吗lock_guard
?lk
是有作用域的,是不是意味着一旦超出作用域锁就会被释放?lk
(lock_guard
)的声明?在开始和更新 lunch_partners
向量之前?如果您需要获取两个锁,如果其他人尝试以相反的顺序获取相同的锁,则可能会遇到死锁。此示例向您展示如何使用
std::lock
来避免死锁。锁定后,互斥体立即被 std::lock_guard
对象采用,这样一旦我们离开作用域,它们就可以解锁。
如前所述,使用 C++17,您可以使用
std::scoped_lock
更简单地完成此操作。
您对为什么 io_mutex 被声明为静态的理解是正确的;这确保了对函数 allocate_lunch_partner 的所有并发调用将在同一个互斥锁上同步。
现在,让我们回答您的其他问题:
是的,这是因为
std::lock_guard
的性质。 std::lock_guard
在其构造函数中获取锁并在其析构函数中释放锁。通过将 std::lock_guard
对象放置在作用域内(用大括号 {} 括起来),退出作用域时将释放锁,因为将调用 std::lock_guard
的析构函数。
是的,完全正确。一旦退出作用域,就会调用
std::lock_guard
的析构函数,并释放锁。这是一种常见模式,用于将锁定的持续时间限制为仅需要同步的代码部分。
为什么有两次作用域 lk (lock_guard) 的声明?在开始和更新 Lunch_partners 向量之前?
这两个独立的作用域锁防护正在同步代码的不同部分:
lunch_partners
向量。再次,这确保了
多个线程的控制台输出不是交错的。本质上,这两个独立的作用域确保即使从多个线程同时调用此函数,消息也会以合理且有序的方式打印。如果在函数的整个持续时间内都持有
io_mutex
锁,则可能会产生瓶颈,并不必要地序列化不需要同步的代码部分。
希望这能够清除此代码中作用域锁防护的使用!