英文:
Understanding cppreference example on lock
问题
-
lk
is scoped with curly braces to limit the scope of the lock. It ensures that the lock is held only within the innermost pair of curly braces. This practice is often used to minimize the duration for which a lock is held to reduce the chances of contention and potential deadlocks. In this case, it's used to protect the output tostd::cout
from concurrent access. -
Yes, you are correct. When a
lock_guard
object (lk
) goes out of scope, it automatically releases the lock it's guarding. The scoping is used to ensure that the lock is released promptly after its purpose is served, in this case, after writing tostd::cout
. -
There are two separate declarations of scoped
lk
objects (lock_guard
) for different purposes. The firstlk
is used to protect the output tostd::cout
, ensuring that concurrent calls toassign_lunch_partner
don't interfere with each other when writing to the console. The second pair oflk1
andlk2
objects is used to locke1.m
ande2.m
, respectively. These locks are adopted from thestd::lock
operation, and the purpose is to synchronize access tolunch_partners
vectors fore1
ande2
. The double scoping ensures that each lock is released when it's no longer needed, and it keeps the critical sections as small as possible.
英文:
While reading on c++ std::lock
, I ran into the following example from the 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);
}
While I do understand the need for making io_mutex
as static
so that its status is shared among concurrent calls to assign_lunch_partner
function (Please correct me if I'm wrong), but I don't understand the following:
- Why the
lk
object (lock_guard
) was scoped? Is it because of the naturelock_guard
? - Then if
lk
is scoped, does not this mean that the lock will be released once gone out of scope? - Why there are twice declaration of scoped
lk
(lock_guard
)? At the beginning and just before updatinglunch_partners
vectors?
答案1
得分: 3
如果你需要获取两个锁,如果其他人尝试以相反的顺序获取相同的锁,可能会遇到死锁问题。此示例演示了如何使用 std::lock
来避免死锁。在锁定后,互斥锁会被 std::lock_guard
对象采用,以便在离开作用域时可以解锁它们。
正如提到的,使用 C++17,你可以更简单地使用 std::scoped_lock
来实现这一点。
英文:
If you need to acquire two locks, you might run into a deadlock if someone else tries to acquire the same locks in the reverse order. This sample shows you to use std::lock
to avoid the deadlock. Immediately after locking, the mutexes are adopted by std::lock_guard
objects so that they can be unlocked as soon as we leave the scope.
As mentioned, with C++17, you can do this more simply with std::scoped_lock
.
答案2
得分: 2
你对io_mutex声明为静态的理解是正确的;这确保了对函数assign_lunch_partner的所有并发调用都会在同一个互斥锁上同步。
现在,让我们回答你的其他问题:
- 为什么lk对象(lock_guard)被限定在作用域内?是因为lock_guard的特性吗?
是的,这是因为std::lock_guard
的特性。std::lock_guard
在其构造函数中获取锁,并在其析构函数中释放锁。通过将std::lock_guard
对象放置在作用域内(由大括号{}括起来),锁将在退出该作用域时被释放,因为std::lock_guard
的析构函数将被调用。
- 那么如果lk被限定在作用域内,这不意味着锁会在作用域外被释放吗?
是的,完全正确。一旦退出作用域,std::lock_guard
的析构函数被调用,锁就会被释放。这是一种常见的模式,用于将锁的持续时间限制在只需要同步的代码部分。
为什么要两次声明scoped lk(lock_guard)?一开始和在更新lunch_partners向量之前?
这两个不同的scoped lock guard在代码的不同部分同步:
- 第一个用于同步输出到控制台,告诉你两名员工正在等待锁。这确保了如果多个线程同时运行此函数,它们的输出不会混在一起。
- 第二个用于同步输出到控制台,告诉你两名员工获得了锁并准备更新
lunch_partners
向量。同样,这确保了来自多个线程的控制台输出不会交错。
实质上,这两个不同的作用域确保了消息以合理和有序的方式打印,即使从多个线程同时调用此函数。如果在整个函数的持续时间内保持io_mutex
锁,可能会创建瓶颈,并不必要地序列化不需要同步的代码部分。
希望这解释了在这段代码中使用scoped lock guard的用法!
英文:
You're correct in your understanding of why io_mutex is declared as static; this ensures that all concurrent calls to the function assign_lunch_partner will synchronize on the same mutex.
Now, let's answer your other questions:
- Why the lk object (lock_guard) was scoped? Is it because of the nature lock_guard?
Yes, it is because of the nature of std::lock_guard
. std::lock_guard
acquires the lock in its constructor and releases the lock in its destructor. By placing the std::lock_guard
object inside a scope (enclosed by curly braces {}), the lock will be released when the scope is exited, as the destructor of std::lock_guard
will be called.
- Then if lk is scoped, does not this mean that the lock will be released once gone out of scope?
Yes, exactly. Once the scope is exited, the destructor for std::lock_guard
is called, and the lock is released. This is a common pattern to limit the duration of a lock to just the section of code that needs synchronization.
Why there are twice declarations of scoped lk (lock_guard)? At the beginning and just before updating lunch_partners vectors?
These two separate scoped lock guards are synchronizing different parts of the code:
- The first one is used to synchronize the output to the console that
tells you that the two employees are waiting for locks. This ensures
that if multiple threads are running this function simultaneously,
their outputs don't get mixed up. - The second one is used to synchronize the output to the console that
tells you that the two employees got the locks and are ready to
update thelunch_partners
vectors. Again, this ensures that the
console output from multiple threads is not interleaved.
Essentially, these two separate scopes ensure that the messages print in a sensible and orderly manner even when this function is being called from multiple threads simultaneously. If the io_mutex
lock were held for the entire duration of the function, it could potentially create a bottleneck and unnecessarily serialize parts of the code that don't need to be synchronized.
Hopefully, that clears up the usage of the scoped lock guards in this code!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论