何时需要一个条件变量,是不是一个互斥量足够?

时间:2012-09-23 09:57:55

标签: multithreading operating-system synchronization mutex condition-variable

我确定互斥是不够的,这就是条件变量概念存在的原因;但它打败了我,当一个条件变量必不可少时,我无法用一个具体的场景来说服自己。

Differences between Conditional variables, Mutexes and Locks问题已接受的答案说条件变量是

  

使用“信令”机制锁定。它在线程需要时使用   等待资源可用。线程可以在CV上“等待”   然后资源生产者可以“发信号”变量,其中   case等待CV的线程得到通知并可以继续   执行

我感到困惑的是,一个线程也可以在互斥锁上等待,当它被发出信号时,只是意味着该变量现在可用,为什么我需要一个条件变量?

P.S。:另外,当我的视力更加偏向于看不到条件变量的目的时,需要一个互斥量来保护条件变量。

6 个答案:

答案 0 :(得分:27)

即使您可以按照您描述的方式使用它们,但互斥锁并非设计用作通知/同步机制。它们旨在提供对共享资源的互斥访问。使用互斥锁来指示一个条件是很尴尬的,我想看起来像这样(Thread1由Thread2发出信号):

线程1:

while(1) {
    lock(mutex); // Blocks waiting for notification from Thread2
    ... // do work after notification is received
    unlock(mutex); // Tells Thread2 we are done
}

线程2:

while(1) {
    ... // do the work that precedes notification
    unlock(mutex); // unblocks Thread1
    lock(mutex); // lock the mutex so Thread1 will block again
}

这有几个问题:

  1. 在Thread1完成“通知后工作”之前,Thread2无法继续“执行通知之前的工作”。使用这种设计,Thread2甚至不是必需的,也就是说,为什么不将“先前工作”和“通知后工作”移动到同一个线程中,因为只有一个可以在给定时间运行!
  2. 如果Thread2无法抢占Thread1,则Thread1会在重复while(1)循环时立即重新锁定互斥锁,并且即使没有通知,Thread1也会执行“通知后工作”。这意味着你必须以某种方式保证Thread2会在Thread1之前锁定互斥锁。你是怎样做的?可能通过睡眠或某些其他特定于操作系统的方式强制执行调度事件,但即使这不能保证根据时间,操作系统和调度算法工作。
  3. 这两个问题并不轻微,事实上,它们都是主要的设计缺陷和潜在的错误。这两个问题的根源是要求在同一个线程中锁定和解锁互斥锁。那么你如何避免上述问题呢?使用条件变量!

    顺便说一句,如果您的同步需求非常简单,您可以使用一个普通的旧信号量,避免条件变量的额外复杂性。

答案 1 :(得分:7)

Mutex用于独占访问共享资源,而条件变量用于等待条件为真。人们可能认为他们可以在没有内核支持的情况下实现条件变量。人们可能提出的一个常见解决方案是“flag + mutex”就像:

lock(mutex)

while (!flag) {
    sleep(100);
}

unlock(mutex)

do_something_on_flag_set();

但它永远不会工作,因为你在等待期间永远不会释放互斥锁,没有其他人可以以线程安全的方式设置标志。这就是为什么我们需要条件变量,当你等待一个条件变量时,你的线程不会保持相关的互斥锁,直到它被发出信号。

答案 2 :(得分:5)

我也在考虑这个问题,最重要的信息,我到处都缺少的是,互斥锁只能在一个线程上拥有(和更改)。因此,如果您有一个生产者和更多的消费者,生产者将不得不等待互斥产生。与cond。他随时都可以制作变量。

答案 3 :(得分:3)

条件var和互斥对可以用二进制信号量和互斥量对替换。使用条件var + mutex时,使用者线程的操作顺序是:

  1. 锁定互斥锁

  2. 等待条件var

  3. 过程

  4. 解锁互斥锁

  5. 生产者线程操作序列是

    1. 锁定互斥锁

    2. 发出条件var

    3. 的信号
    4. 解锁互斥锁

    5. 使用sema + mutex对时的相应使用者线程序列是

      1. 等待二进制sema

      2. 锁定互斥锁

      3. 检查预期条件

      4. 如果条件为真,则处理。

      5. 解锁互斥锁

      6. 如果步骤3中的条件检查为假,请返回步骤1.

      7. 生产者线程的序列是:

        1. 锁定互斥锁

        2. 发布二进制sema

        3. 解锁互斥锁

        4. 正如您所看到的,当使用条件var时,步骤3中的无条件处理被替换为使用二进制sema时步骤3和步骤4中的条件处理。

          原因是当使用sema + mutex时,在竞争条件下,另一个消费者线程可能会在步骤1和2之间潜入并处理/使条件无效。使用条件var时不会发生这种情况。使用条件var时,在步骤2之后保证条件为真。

          二进制信号量可以用常规计数信号量替换。这可能导致步骤6到步骤1循环几次。

答案 4 :(得分:1)

您需要条件变量,与互斥锁一起使用(每个cond.var。属于互斥锁),以指示从一个线程到另一个线程的状态(条件)的变化。这个想法是一个线程可以等到某个条件变为真。这些条件是程序特定的(即“队列为空”,“矩阵很大”,“某些资源几乎耗尽”,“某些计算步骤已完成”等)。互斥锁可能有几个相关的条件变量。并且您需要条件变量,因为这些条件可能并不总是表示为“互斥锁被锁定”(因此您需要将条件的变化广播到其他线程)。

阅读一些优秀的posix线程教程,例如: this tutorialthatthat一个。更好的是,阅读一本好的pthread书。请参阅this question

另请阅读Advanced Unix ProgrammingAdvanced Linux Programming

P.S。并行性和线程是难以掌握的概念。花点时间阅读,试验并再次阅读。

答案 5 :(得分:0)

我认为这是实施定义的 互斥锁是否足够取决于您是否将互斥锁视为关键部分的机制或其他内容。

http://en.cppreference.com/w/cpp/thread/mutex/unlock中所述,

  

互斥锁必须由当前执行线程锁定,否则行为未定义。

这意味着一个线程只能解锁一个在C ++中被自己锁定/拥有的互斥锁 但在其他编程语言中,您可以在进程之间共享互斥锁。

因此,区分这两个概念可能只是性能考虑因素,复杂的所有权识别或进程间共享对于简单的应用程序来说是不值得的。

例如,您可以使用额外的互斥锁修复@ slowjelj的情况(可能是不正确的修复):

线程1:

lock(mutex0);
while(1) {
    lock(mutex0); // Blocks waiting for notification from Thread2
    ... // do work after notification is received
    unlock(mutex1); // Tells Thread2 we are done
}

线程2:

while(1) {
    lock(mutex1); // lock the mutex so Thread1 will block again
    ... // do the work that precedes notification
    unlock(mutex0); // unblocks Thread1
}

但是你的程序会抱怨你已经触发了编译器留下的断言(例如"解锁无主互斥"在Visual Studio 2015中)。

相关问题