首页 文章

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

提问于
浏览
35

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

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

使用“信号”机制锁定 . 当线程需要等待资源变得可用时使用它 . 线程可以在CV上“等待”,然后资源生成器可以“发出信号”变量,在这种情况下,等待CV的线程会得到通知并可以继续执行

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

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

6 回答

  • 1

    即使您可以按照描述的方式使用它们,但互斥锁并非设计用作通知/同步机制 . 它们旨在提供对共享资源的互斥访问 . 使用互斥锁来发出信号是很尴尬的,我想这看起来像这样(Thread2由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
    }
    

    这有几个问题:

    • 在Thread1完成"work after notification"之前,Thread2无法继续"do the work that precedes notification" . 使用这种设计,Thread2甚至不是必需的,也就是说,为什么不将"work that precedes"和"work after notification"移动到同一个线程中,因为只有一个可以在给定时间运行!

    • 如果Thread2无法抢占Thread1,则Thread1会在重复while(1)循环时立即重新锁定互斥锁,并且即使没有通知,Thread1也会执行"work after notification" . 这意味着你必须以某种方式保证Thread2会在Thread1之前锁定互斥锁 . 你是怎样做的?可能通过睡眠或某些其他特定于操作系统的方式强制执行调度事件,但即使这不能保证根据时间,操作系统和调度算法工作 .

    这两个问题并不轻微,事实上,它们都是主要的设计缺陷和潜在的错误 . 这两个问题的根源是要求在同一个线程中锁定和解锁互斥锁 . 那么你如何避免上述问题呢?使用条件变量!

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

  • 26

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

    lock(mutex)
    
    while (!flag) {
        sleep(100);
    }
    
    unlock(mutex)
    
    do_something_on_flag_set();
    

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

  • 5

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

  • 0

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

    • 锁定互斥锁

    • 等待条件变量

    • 过程

    • 解锁互斥锁

    生产环境 者线程操作序列是

    • 锁定互斥锁

    • 发出条件变量的信号

    • 解锁互斥锁

    使用sema互斥锁对时的相应使用者线程序列是

    • 等待二进制sema

    • 锁定互斥锁

    • 检查预期情况

    • 如果条件为真,则处理 .

    • 解锁互斥锁

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

    生产环境 者线程的顺序是:

    • 锁定互斥锁

    • 发布二进制sema

    • 解锁互斥锁

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

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

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

  • 7

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

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

    另请阅读Advanced Unix ProgrammingAdvanced Linux Programming

    附:并行性和线程是难以掌握的概念 . 花时间阅读,实验并再次阅读 .

  • 3

    我认为这是实施定义的 .
    互斥锁是否足够取决于您是否将互斥锁视为关键部分或更多部分的机制 .

    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中“解锁无主互斥锁”) .

相关问题