【问题标题】:Why we keep mutex instead of declaring it before guard every time?为什么我们保留互斥体而不是每次都在守卫之前声明它?
【发布时间】:2018-03-09 10:44:19
【问题描述】:

请考虑这种经典方法,我已对其进行了简化以突出确切的问题:

#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
 }

【问题讨论】:

  • 就像每个人都自带锁一样,他们在进入你家时会锁定/解锁
  • 只回答一个简单的问题:互斥体的目的是什么?完成此操作后,您也有了问题的答案。
  • 不是很明显!

标签: c++ multithreading c++11 mutex race-condition


【解决方案1】:

假设两个线程并行调用modify。所以每个线程都有自己的自己的新互斥体。所以guard 没有任何作用,因为每个守卫都在锁定不同的互斥体。您试图保护免受竞争条件影响的资源将被公开。

【讨论】:

  • 您已经解决了为什么必须在访问共享资源的线程之间共享互斥体实例,但 OP 提出了一个有趣的观点,即共享资源的更广泛范围。考虑将互斥锁设为modify() 中的静态局部变量。下一个问题变成“还有哪些其他方法正在访问共享资源?”这导致了将互斥锁定义在与共享资源相同的范围内的做法。
  • @DavidThomas:即使modify 是唯一需要锁定的东西,static 局部变量也会导致 modify 锁定,即使您正在访问 不同 对象类型的实例。 就是为什么它需要每个实例。
【解决方案2】:

误解来自mutex是什么,lock_guard有什么用处。

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

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

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

希望对您有所帮助。

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

      【解决方案4】:

      线程同步涉及检查是否有另一个线程在执行临界区。 mutex 是保存状态的对象,以便我们检查它是否被线程“锁定”。另一方面,lock_guard 是一个包装器,在初始化时 locks mutex 和在销毁时 unlocks 它。

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

      【讨论】:

        【解决方案5】:

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

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多