为什么我们保留互斥锁而不是每次都守卫前声明它

Why we keep mutex instead of declaring it before guard every time?

本文关键字:守卫 声明 保留 我们 为什么      更新时间:2023-10-16

请考虑这种经典方法,我将其简化以突出确切的问题:

#include <iostream>
#include <mutex>
using namespace std;
class Test
{
    public:
        void modify()
        {
            std::lock_guard<std::mutex> guard(m_);
            // modify data
        }
    private:
    /// some private data
    std::mutex m_;
};

这是使用std::mutex来避免数据竞争的经典方法。

问题是为什么我们在课堂上多留std::mutex?为什么我们不能每次都像这样在std::lock_guard宣布之前宣布它?

void modify()
{
    std::mutex m_;
    std::lock_guard<std::mutex> guard(m_);
   // modify data
 }

假设两个线程并行调用modify。因此,每个线程都有自己的新互斥锁。因此,guard不起作用,因为每个防护装置都锁定了不同的互斥锁。您尝试保护的资源免受争用条件的影响将被公开。

误解来自mutex是什么以及lock_guard有什么好处。

互斥锁

是不同线程之间共享的对象,每个线程都可以锁定和释放互斥锁。这就是不同线程之间的同步的工作方式。因此,您也可以使用 m_.lock()m_.unlock(),但您必须非常小心,确保函数中的所有代码路径(包括异常退出(实际上都会解锁互斥锁。

为了避免缺少解锁的陷阱,lock_guard是一个包装对象,它在创建包装对象时锁定互斥锁,并在销毁包装器对象时解锁互斥锁。由于包装器对象是具有自动存储持续时间的对象,因此您永远不会错过解锁 - 这就是原因。

本地

互斥锁没有意义,因为它是本地的,而不是共享资源。本地lock_guard完全有意义,因为自动存储持续时间可防止丢失锁定/解锁。

希望对您有所帮助。

这一切都取决于您要防止并行执行的内容的上下文。

多个线程尝试访问同一个互斥锁对象时,互斥锁将起作用。因此,当 2 个线程尝试访问并获取互斥对象的锁时,只有一个线程会成功。

现在在第二个示例中,如果两个线程调用modify()则每个线程都有自己的互斥锁实例,因此没有什么可以阻止它们并行运行该函数,就好像没有互斥锁一样。

所以回答你的问题:这取决于上下文。该设计的任务是确保所有不应并行执行的线程将在关键部分命中相同的互斥对象。

线程同步涉及检查是否有另一个线程执行关键部分。mutex是保存状态的对象,供我们检查它是否被线程"锁定"。 另一方面,lock_guard是一个包装器,它在初始化时lock mutex,并在销毁期间unlock它。

意识到这一点后,应该更清楚为什么所有lock_guard都需要访问的mutex只有一个实例 - 他们需要检查是否清楚地针对同一对象进入关键部分。在问题的第二个代码段中,每个函数调用都会创建一个单独的mutex,该只能在其本地上下文中看到和访问。

你需要类级别的互斥锁。否则,每个线程都有一个互斥锁,因此互斥锁不起作用。

如果出于某种原因您不希望将互斥锁存储在类属性中,则可以使用静态互斥锁,如下所示。

void modify()
{
    static std::mutex myMutex;
    std::lock_guard<std::mutex> guard(myMutex);
    // modify data
}

请注意,这里所有类实例只有 1 个互斥锁。如果互斥锁存储在属性中,则每个类实例将有一个互斥锁。根据您的需求,您可能更喜欢一种解决方案或另一种解决方案。