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