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

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

请考虑这种class合理的方法,我已对其进行了简化以突出显示确切的问题:

#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 避免数据竞争的 class 逻辑方法。

问题是为什么我们要在 class 中保留额外的 std::mutex?为什么我们不能像这样在std::lock_guard的声明之前每次都声明呢?

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

假设两个线程并行调用 modify。所以每个线程都有自己的,新的互斥体。所以 guard 没有效果,因为每个守卫都锁定了不同的互斥体。您试图保护的资源 race-conditions 将被公开。

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

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

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

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

希望对您有所帮助。

这完全取决于你想防止并行执行的内容

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

现在在你的第二个例子中,如果两个线程调用 modify()每个线程都会有自己的互斥体实例,所以没有什么可以阻止它们 运行 并行运行,就好像没有互斥锁一样。

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

线程同步涉及检查是否有另一个线程在执行临界区。 A mutex is the objects that holds the state for us to check if it was "locked" by a thread. lock_guard on the other hand is a wrapper that locks the mutex on initialization and unlocks 它在销毁期间。

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

您需要 class 级别的互斥体。否则,每个线程都有自己的互斥锁,因此互斥锁无效。

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

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

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