【问题标题】:c++ understanding a lock_guard and mutex in multithreadingc++ 理解多线程中的 lock_guard 和 mutex
【发布时间】:2018-11-24 04:27:34
【问题描述】:

我不清楚互斥锁和锁是如何工作的。

我有一个对象(my_class),我在主线程中从对象中添加、删除和读取数据。在我的第二个线程中,我想检查我的对象中的一些数据。问题是,在从第二个线程读取数据期间,当我在主线程中删除对象时,可能会导致应用程序崩溃。

因此,我在第二个线程中创建了std::lock_guard<std::mutex> lock(mymutex)。

我创建了测试并且使用这个 lock_guard 它永远不会崩溃。但我不知道我是否也需要在主线程中使用锁。

问题是,当第二个线程锁定互斥体并读取数据并且主线程想要从对象中删除数据但没有锁定时会发生什么? 否则当第二个线程想要锁定互斥体并从对象中读取数据而主线程从对象中删除数据时会发生什么?

【问题讨论】:

  • std::lock_guard 只是一个 RAII 包装器,用于锁定互斥锁并在互斥锁超出范围时自动解锁。这样您就不必手动操作了。如何以及在何处使用它有点过于宽泛。
  • 你需要两把锁
  • this 是否充分回答了您的问题?您的困惑似乎归结为根本不知道互斥锁的用途。

标签: c++ multithreading mutex


【解决方案1】:

暂时忘记std::lock_guard。这只是方便(一个非常有用的,但仍然只是方便)。同步原语是互斥体本身。

Mutex 是 MUTual EXclusion 的缩写。这是一种同步原语,它允许一个线程排除其他线程对受互斥锁保护的任何内容的访问。它通常是共享数据,但也可以是任何东西(例如一段代码)。

在您的情况下,您拥有在两个线程之间共享的数据。为了防止潜在的灾难性并发访问,所有对该数据的访问必须受到某种保护。使用互斥锁是明智之举。

因此,您在概念上将数据与互斥锁捆绑在一起,并且每当任何代码想要访问(读取、修改、写入、删除……)数据时,它必须首先锁定互斥锁。由于不再有一个线程可以在任何时候锁定互斥体,因此数据访问将正确同步并且不会发生竞争条件。

通过上述方式,所有访问数据的代码将如下所示:

mymutex.lock();
/* do whatever necessary with the shared data */
mymutex.unlock();

没关系,只要

  1. 您永远不会忘记正确匹配 lock 和 unlock 调用,即使存在多个返回路径,并且
  2. 互斥锁锁定时执行的操作不会引发异常

由于上述几点很难手动完成(它们是一个很大的维护负担),因此有一种方法可以将它们自动化。这就是我们在开始时搁置的std::lock_guard 便利。这只是一个简单的RAII 类,它在其构造函数中的互斥体上调用lock(),在其析构函数中调用unlock()。使用锁守卫,访问共享数据的代码将如下所示:

{
  std::lock_guard<std::mutex> g(mymutex);
  /* do whatever necessary with the shared data */
}

这保证了操作完成时互斥锁将被正确解锁,无论是通过可能的许多 return(或其他跳转)语句之一,还是通过异常。

【讨论】:

  • 注意:在您的情况下,“共享数据”包括指向您的对象的指针。您需要在删除对象之前获取互斥锁并将指针设置为 nullptr。并在线程中获取互斥体并在使用之前检查对象!= nullptr。
  • 如果我理解,互斥锁必须在两个线程上,它们使用同一个对象,对吧?现在我扩展我的问题,因为我忘了问,当互斥锁被锁定在一个线程上并且第二次尝试锁定时会发生什么?它会等待解锁吗?如果是,我该如何防止不等待并继续代码(当然,继续代码跳过捆绑在互斥锁和解锁中的代码)?
  • @Wanderer 是的,必须在两个线程上锁定互斥锁。是的,lock 阻塞,直到互斥体可用。互斥体上还有try_lock,它允许您在互斥体无法锁定时执行其他操作。
  • Re,“它可以是任何东西(例如一段代码)。”如今,代码几乎从不可变。如果它不是可变的,那么就没有必要保护它。阻止多个线程同时调用同一个例程的唯一原因是防止它们访问该例程同时操作的任何数据。
  • @jameslarge 当然,我不是指mutating 代码。我的意思是执行它。虽然共享数据是迄今为止相互排斥的最常见原因,但也可能有其他原因(例如共享物理资源)。
【解决方案2】:

std::lock_guardstd::mutex 是上面提到的捷径,但对于并发控制流至关重要,当互斥体有意义时,你总是拥有它们!

如果受保护的块引发异常,则不会在块本身内部处理,脆弱模式

mymutex.lock();
/* do anything but raising an exception here! */
mymutex.unlock();

不会解锁互斥锁,并且等待互斥锁的其他一些控制流可能会卡在死锁中。

稳健的模式

{
    std::lock_guard<std::mutex> guard(mymutex);
    /* do anything here! */
}

当方块离开时,无论如何都会对mymutex执行解锁。

另一个相关用例是同步访问某些属性

int getAttribute()
{
    std::lock_guard<std::mutex> guard(mymutex);
    return attribute;
}

这里,如果没有 lock_guard,你需要将返回值分配给其他变量,然后才能解锁互斥锁,这又是两个步骤,并且再次不处理异常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-17
    • 2013-12-29
    • 1970-01-01
    • 2017-03-28
    • 1970-01-01
    • 2013-08-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多