理解cppreference上关于锁定的示例

huangapple go评论71阅读模式
英文:

Understanding cppreference example on lock

问题

  1. 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 to std::cout from concurrent access.

  2. 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 to std::cout.

  3. There are two separate declarations of scoped lk objects (lock_guard) for different purposes. The first lk is used to protect the output to std::cout, ensuring that concurrent calls to assign_lunch_partner don't interfere with each other when writing to the console. The second pair of lk1 and lk2 objects is used to lock e1.m and e2.m, respectively. These locks are adopted from the std::lock operation, and the purpose is to synchronize access to lunch_partners vectors for e1 and e2. 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:

  1. Why the lk object (lock_guard) was scoped? Is it because of the nature lock_guard?
  2. Then if lk is scoped, does not this mean that the lock will be released once gone out of scope?
  3. Why there are twice declaration of scoped lk (lock_guard)? At the beginning and just before updating lunch_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在代码的不同部分同步:

  1. 第一个用于同步输出到控制台,告诉你两名员工正在等待锁。这确保了如果多个线程同时运行此函数,它们的输出不会混在一起。
  2. 第二个用于同步输出到控制台,告诉你两名员工获得了锁并准备更新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:

  1. 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.
  2. 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 the lunch_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!

huangapple
  • 本文由 发表于 2023年8月4日 22:05:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76836686.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定