条件变量、互斥锁和锁之间的区别

66
例如,C++11接口
我在弄清楚何时使用这些东西(cv、mutex和lock)方面遇到了困难。 有人能解释一下或指点一下资源吗?
提前感谢。
3个回答

88
在您所提到的页面中,“互斥锁”是实际的低级同步原语。您可以获取并释放互斥锁,每次只能有一个线程获取它(因此它是一种同步原语)。递归互斥锁是一种可以被相同线程多次获取的互斥锁,然后需要由相同的线程释放相应次数才能被其他线程获取。
这里的“锁”只是一个C++包装类,它在其构造函数中获取互斥锁并在析构函数中释放它。它用于为C++作用域建立同步。
条件变量是更高级/高层次的同步原语形式,它结合了锁和“信号”机制。当线程需要等待资源变得可用时,它会被使用。线程可以在条件变量上“等待”,然后资源生产者可以“通知”变量,在这种情况下,等待CV的线程将收到通知并可以继续执行。互斥锁与CV结合起来以避免竞争条件,在该条件下,一个线程开始在CV上等待,同时另一个线程想要发信号;然后无法控制信号是否传递或丢失。

7
使用条件变量进行等待(线程1)和通知(线程2),是否与加锁(线程1)和解锁(线程2)的过程完全相同? - Ronny Brendel
25
@hydroes:不。如果线程1正在等待一个锁,那么其他某个线程必须持有它才能使线程1保持阻塞状态。一旦该线程释放了锁,线程1就可以解除阻塞以获取互斥锁。使用条件变量时,任何线程都可以发出信号来解除等待者的阻塞,而不仅仅是一些特殊的持有锁的线程。此外,您可以广播一个条件变量来释放所有等待的线程。 - Steve Jessop
1
缺失的文档,谢谢! - spacediver
1
@Antti Huima:“互斥锁与条件变量结合使用,以避免竞态条件,在此情况下,一个线程开始等待条件变量的同时,另一个线程想要发出信号;然后就无法控制信号是否被传递或丢失。”这可能是其他地方缺少的关键解释? - asinix
“一个mutex和CV的组合可以避免竞争条件”这部分是不正确的。当条件变量上没有等待者时,通知会被丢失。虚假唤醒也会发生。Mutex和条件变量更多地用于等待共享状态的改变。为了消除竞争条件,在更新或读取共享状态时必须锁定mutex。与答案中的建议相反,当发出条件变量信号时不需要锁定mutex(只需阅读std::condition_variable::notify_onepthread_cond_signal的文档)。 - Maxim Egorushkin

5
这个问题已经得到了回答。我只是补充一下,可能有助于决定何时使用这些同步原语。
简单地说,互斥锁用于保证多个线程的关键部分对共享资源的互斥访问。锁是一个通用术语,但二元互斥锁可以用作锁。在现代 C++ 中,我们使用 lock_guard 和类似的对象来利用 RAII 简化和安全地使用互斥锁。条件变量是另一种常与互斥锁结合使用的原语,用于创建所谓的监视器

我很难弄清楚何时使用这些东西(cv、mutex 和 lock)。请问有人能解释或指向资源吗?

使用互斥锁来保证对某个资源的互斥访问。它是广泛解决并发问题的默认方案。如果您想在C++中保护一个作用域,可以使用lock_guard。mutex由lock_guard处理。只需在作用域中创建lock_guard,并使用一个mutex进行初始化,然后C ++将为您完成其余工作。当作用域从堆栈中移除时(包括抛出异常或从函数返回),mutex将被释放。这是RAII的思想,而lock_guard是另一个资源处理程序。

有一些并发问题不仅使用互斥锁就能轻松解决,而且简单的解决方案可能会导致复杂性或低效率。例如,生产者-消费者问题 就是其中之一。如果我们想要实现一个消费者线程从与生产者共享的缓冲区中读取项目,我们应该用互斥锁来保护缓冲区,但是,如果没有使用条件变量,我们应该锁定互斥锁,检查缓冲区,如果不为空,则读取一个项目,解锁它并等待一段时间,再次锁定它并继续。如果缓冲区经常为空(忙等待),这将是浪费时间的,而且还会有很多锁定、解锁和休眠。

我们需要解决生产者消费者问题的方案必须更简单和更高效。这里有一个监视器(互斥锁+条件变量)可以帮助我们。我们仍然需要互斥锁来保证互斥访问,但是条件变量让我们可以睡眠并等待特定条件。这个条件就是生产者向缓冲区添加项目。生产者线程通知消费者线程缓冲区中有项目,消费者醒来并获取项目。简单地说,生产者锁定互斥锁,将某些内容放入缓冲区,通知消费者。消费者锁定互斥锁,在等待条件时休眠,当缓冲区中有内容时醒来并从缓冲区中获取内容。这是一种更简单和更高效的解决方案。
下次你面对并发问题时,请考虑这种方式:如果你需要互斥访问某些内容,请使用互斥锁。如果你想更安全和更简单,请使用lock_guard。如果问题涉及等待另一个线程必须发生的条件,则可能需要一个条件变量。
作为一个基本的准则,首先要分析你的问题并尝试找到一个与你类似的著名并发问题(例如,请参见this page中的同步部分中的经典问题)。阅读有关所提出的解决方案的文章,以挑选最佳解决方案。你可能需要进行一些定制。

5

我对C++0x不是很熟悉,因此请谨慎接受我的回答。

关于Mutex和Lock的区别:从您发布的文档中可以看出,mutex是表示操作系统mutex的对象,而lock是一个持有mutex的对象,以便实现RAII模式

条件变量是一种方便的机制,可以将阻塞/信号机制(信号+等待)与互斥机制相关联,但在操作系统中保持它们分离,使您作为系统程序员可以选择condvar和mutex之间的关联。 (用于处理多组并发访问的对象)Rob Krten在他的QNX书籍的在线章节中有一些关于condvars的好的解释

至于一般参考资料:这本书(尚未出版)看起来很有趣。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接