了解锁上的 cppreference 示例

问题描述 投票:0回答:2

在阅读 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
函数的并发调用之间共享其状态(如果我错了,请纠正我),但我不明白以下内容:

  1. 为什么
    lk
    对象 (
    lock_guard
    ) 被限定范围?是因为天生的原因吗
    lock_guard
  2. 那么如果
    lk
    是有作用域的,是不是意味着一旦超出作用域锁就会被释放?
  3. 为什么有两次范围
    lk
    lock_guard
    )的声明?在开始和更新
    lunch_partners
    向量之前?
c++ c++11 scope locking lock-guard
2个回答
2
投票

如果您需要获取两个锁,如果其他人尝试以相反的顺序获取相同的锁,则可能会遇到死锁。此示例向您展示如何使用

std::lock
来避免死锁。锁定后,互斥体立即被
std::lock_guard
对象采用,这样一旦我们离开作用域,它们就可以解锁。

如前所述,使用 C++17,您可以使用

std::scoped_lock
更简单地完成此操作。


0
投票

您对为什么 io_mutex 被声明为静态的理解是正确的;这确保了对函数 allocate_lunch_partner 的所有并发调用将在同一个互斥锁上同步。

现在,让我们回答您的其他问题:

  • 为什么 lk 对象(lock_guard)被限定作用域?是因为lock_guard的本质吗?

是的,这是因为

std::lock_guard
的性质。
std::lock_guard
在其构造函数中获取锁并在其析构函数中释放锁。通过将
std::lock_guard
对象放置在作用域内(用大括号 {} 括起来),退出作用域时将释放锁,因为将调用
std::lock_guard
的析构函数。

  • 那么如果lk是有作用域的,是不是意味着一旦超出作用域锁就会被释放?

是的,完全正确。一旦退出作用域,就会调用

std::lock_guard
的析构函数,并释放锁。这是一种常见模式,用于将锁定的持续时间限制为仅需要同步的代码部分。

为什么有两次作用域 lk (lock_guard) 的声明?在开始和更新 Lunch_partners 向量之前?

这两个独立的作用域锁防护正在同步代码的不同部分:

  1. 第一个用于将输出同步到控制台 告诉您两个员工正在等待锁。这确保了 如果多个线程同时运行这个函数, 他们的输出不会混淆。
  2. 第二个用于将输出同步到控制台 告诉您两名员工已拿到锁并准备好 更新
    lunch_partners
    向量。再次,这确保了 多个线程的控制台输出不是交错的。

本质上,这两个独立的作用域确保即使从多个线程同时调用此函数,消息也会以合理且有序的方式打印。如果在函数的整个持续时间内都持有

io_mutex
锁,则可能会产生瓶颈,并不必要地序列化不需要同步的代码部分。

希望这能够清除此代码中作用域锁防护的使用!

© www.soinside.com 2019 - 2024. All rights reserved.