c++ 理解多线程中的 lock_guard 和互斥量
c++ understanding a lock_guard and mutex in multithreading
我不清楚互斥锁和锁是如何工作的。
我有一个对象 (my_class),我在主线程中从对象中添加、删除和读取数据。在我的第二个线程中,我想检查我的对象中的一些数据。问题是,在从第二个线程读取数据的过程中,当我在主线程中删除对象时,可能会导致应用程序崩溃。
因此我在第二个线程中创建了 std::lock_guard<std::mutex> lock(mymutex)
。
我创建了测试,使用这个 lock_guard 它永远不会崩溃。但我不知道我是否也需要在主线程中使用锁。
问题是,当第二个线程锁定互斥量并读取数据并且主线程想要从对象中删除数据但没有锁定时会发生什么?
否则,当主线程从对象中删除数据时,第二个线程想要锁定互斥锁并从对象中读取数据时会发生什么?
暂时忘掉std::lock_guard
。这只是方便(非常 有用,但仍然只是方便)。同步原语是互斥锁本身。
Mutex 是 MUTual EXclusion 的缩写。它是一种同步原语,允许一个线程排除其他线程访问受互斥锁保护的任何内容。它通常是共享数据,但它可以是任何东西(例如一段代码)。
在你的例子中,你有两个线程共享的数据。为了防止潜在的灾难性并发访问,所有 对该数据的访问必须受到某种保护。为此使用互斥体是明智的。
因此,您在概念上将数据与互斥锁捆绑在一起,并且每当 任何 代码想要访问(读取、修改、写入、删除...)数据时,它必须首先锁定互斥量。由于在任何时候都不会再有一个线程锁定互斥锁,因此数据访问将正确同步并且不会出现竞争条件。
有了上面的代码,访问数据的所有代码将如下所示:
mymutex.lock();
/* do whatever necessary with the shared data */
mymutex.unlock();
可以,只要
- 你永远不会忘记正确匹配
lock
和 unlock
调用,即使存在多个 return 路径,并且
- 互斥体锁定时完成的操作不会抛出异常
由于以上几点很难手动正确处理(它们是一个很大的维护负担),所以有一种方法可以使它们自动化。这就是我们在开始时搁置的 std::lock_guard
便利。它只是一个简单的 RAII class ,它在其构造函数中调用互斥量 lock()
并在其析构函数中调用 unlock()
。使用锁保护器,访问共享数据的代码将如下所示:
{
std::lock_guard<std::mutex> g(mymutex);
/* do whatever necessary with the shared data */
}
这保证了在操作完成时互斥量将被正确解锁,无论是通过潜在的许多 return
(或其他跳转)语句之一,还是通过异常。
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,您需要将 return 值分配给其他一些变量,然后才能解锁互斥锁,这又是两个步骤,并且不会处理异常。
我不清楚互斥锁和锁是如何工作的。
我有一个对象 (my_class),我在主线程中从对象中添加、删除和读取数据。在我的第二个线程中,我想检查我的对象中的一些数据。问题是,在从第二个线程读取数据的过程中,当我在主线程中删除对象时,可能会导致应用程序崩溃。
因此我在第二个线程中创建了 std::lock_guard<std::mutex> lock(mymutex)
。
我创建了测试,使用这个 lock_guard 它永远不会崩溃。但我不知道我是否也需要在主线程中使用锁。
问题是,当第二个线程锁定互斥量并读取数据并且主线程想要从对象中删除数据但没有锁定时会发生什么? 否则,当主线程从对象中删除数据时,第二个线程想要锁定互斥锁并从对象中读取数据时会发生什么?
暂时忘掉std::lock_guard
。这只是方便(非常 有用,但仍然只是方便)。同步原语是互斥锁本身。
Mutex 是 MUTual EXclusion 的缩写。它是一种同步原语,允许一个线程排除其他线程访问受互斥锁保护的任何内容。它通常是共享数据,但它可以是任何东西(例如一段代码)。
在你的例子中,你有两个线程共享的数据。为了防止潜在的灾难性并发访问,所有 对该数据的访问必须受到某种保护。为此使用互斥体是明智的。
因此,您在概念上将数据与互斥锁捆绑在一起,并且每当 任何 代码想要访问(读取、修改、写入、删除...)数据时,它必须首先锁定互斥量。由于在任何时候都不会再有一个线程锁定互斥锁,因此数据访问将正确同步并且不会出现竞争条件。
有了上面的代码,访问数据的所有代码将如下所示:
mymutex.lock();
/* do whatever necessary with the shared data */
mymutex.unlock();
可以,只要
- 你永远不会忘记正确匹配
lock
和unlock
调用,即使存在多个 return 路径,并且 - 互斥体锁定时完成的操作不会抛出异常
由于以上几点很难手动正确处理(它们是一个很大的维护负担),所以有一种方法可以使它们自动化。这就是我们在开始时搁置的 std::lock_guard
便利。它只是一个简单的 RAII class ,它在其构造函数中调用互斥量 lock()
并在其析构函数中调用 unlock()
。使用锁保护器,访问共享数据的代码将如下所示:
{
std::lock_guard<std::mutex> g(mymutex);
/* do whatever necessary with the shared data */
}
这保证了在操作完成时互斥量将被正确解锁,无论是通过潜在的许多 return
(或其他跳转)语句之一,还是通过异常。
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,您需要将 return 值分配给其他一些变量,然后才能解锁互斥锁,这又是两个步骤,并且不会处理异常。