【问题标题】:pthread_cond_wait versus semaphorepthread_cond_wait 与信号量
【发布时间】:2010-09-09 09:42:30
【问题描述】:

使用pthread_cond_wait 或使用信号量的优缺点是什么? 我正在等待这样的状态变化:

pthread_mutex_lock(&cam->video_lock);
while(cam->status == WAIT_DISPLAY) {
    pthread_cond_wait(&cam->video_cond, &cam->video_lock);
}
pthread_mutex_unlock(&cam->video_lock);

使用正确初始化的信号量,我想我可以这样做:

while(cam->status == WAIT_DISPLAY) {
    sem_wait(&some_semaphore);
}

每种方法的优缺点是什么?

【问题讨论】:

  • 我刚刚意识到“正确初始化的信号量”定义不明确。信号量是设置为 1 还是设置为 0?我会说它应该设置为 0。那么,信号量是否保护 cam->status?
  • 在第二个 sn-p 中,如其他答案所述,您的线程将在互斥锁未释放时被阻止。所以你的sem_wait 方法永远不会返回,因为没有其他线程可以获取互斥锁并调用sem_signal。但是,如果我在 sem_wait 之前释放 mutex 并在等待后再次要求它,我不会怎么办。我知道这些步骤不是原子的,那么会发生什么?

标签: c multithreading semaphore


【解决方案1】:

信号量完全适合生产者-消费者模型,尽管它还有其他用途。您的程序逻辑负责确保根据等待次数发布正确数量的帖子。如果您发布信号量并且没有人在等待它,那么当他们等待时,他们会立即继续。如果您的问题可以用信号量的计数值来解释,那么使用信号量应该很容易解决。

条件变量在某些方面更加宽容。例如,您可以使用 cond_broadcast 唤醒所有服务员,而生产者不知道有多少服务员。如果你 cond_signal 一个没有人在等待它的 condvar,那么什么也不会发生。如果您不知道是否会有听众感兴趣,这很好。这也是为什么侦听器应该在等待之前始终检查持有的互斥锁的状态 - 如果他们不这样做,那么他们可能会错过一个信号并且直到下一个信号才会唤醒(可能永远不会)。

因此,条件变量适用于通知相关方状态已更改:您获取互斥锁、更改状态、发送信号(或广播)condvar 并释放互斥锁。如果这描述了您的问题,那么您就处于 condvar 领域。如果不同的听众对不同的状态感兴趣,你可以广播,他们会依次唤醒,判断他们是否找到了他们想要的状态,如果没有,就再等。

用互斥锁和信号量尝试这种事情确实很麻烦。当您想要获取互斥体,检查某些状态,然后等待信号量进行更改时,问题就来了。除非您可以原子地释放互斥锁并等待信号量(在 pthreads 中您不能这样做),否则您最终会在持有互斥锁的同时等待信号量。这会阻止互斥体,这意味着其他人无法接受它来进行您关心的更改。因此,您会很想以取决于您的特定要求的方式添加另一个互斥锁。也许是另一个信号量。结果通常是带有有害竞争条件的错误代码。

条件变量避免了这个问题,因为调用 cond_wait 会自动释放互斥体,释放它以供其他人使用。在 cond_wait 返回之前重新获得互斥锁。

IIRC 可以仅使用信号量来实现一种 condvar,但如果您要与 condvar 一起实现的互斥锁必须具有 trylock,那么这是一个严重的问题,并且定时等待已经结束。不建议。所以不要假设你可以用 condvar 做的任何事情都可以用信号量来完成。当然,互斥体可以具有信号量所缺乏的良好行为,主要是避免优先级反转。

【讨论】:

    【解决方案2】:

    条件允许你做一些信号量不会做的事情。

    例如,假设您有一些代码需要互斥体,称为m。然而,它需要等到其他线程完成它们的任务,所以它等待一个名为s 的信号量。现在任何需要m 的线程都被阻止运行,即使拥有m 的线程正在等待s。这些情况可以使用条件来解决。当您等待条件时,当前持有的互斥锁被释放,因此其他线程可以获取互斥锁。回到我们的例子,假设使用条件c 而不是s。我们的线程现在获取m,然后有条件地等待c。这将释放m,以便其他线程可以继续。当c 可用时,m 被重新获取,我们原来的线程可以继续愉快地继续下去。

    条件变量还允许您让所有线程等待条件变量通过pthread_cond_broadcast 继续。此外,它还允许您执行定时等待,这样您就不会永远等待。

    当然,有时您不需要条件变量,因此根据您的要求,其中一个可能会更好。

    【讨论】:

    • 这是一个很好的答案。补充说明一下,条件语句相对于所描述的互斥锁和信号量组合的独特之处在于条件以原子方式操作互斥锁/信号量对!
    【解决方案3】:

    第二个sn-p很活泼,不要那样做。

    其他答案对相对优点进行了很好的讨论;我要补充一点,pthread_cond_broadcast 是条件变量的明显优势。

    除此之外,我只是更习惯于为此设置条件变量,因为它们是您在 Java 中使用的,即使它们可以帮助您在检查共享标志时避免竞争。

    确实,在第二个 sn-p 中,您没有任何锁来保护 cam->status 的读取,因此它是通过数据竞争访问的。在这个特定的示例中,大多数平台都会让您侥幸逃脱,但根据 POSIX 和下一个 C/C++ 标准的内存模型,它具有未定义的语义。

    事实上,如果另一个线程分配了一个新的 cam 结构并覆盖了 cam,那么真正的竞争条件是可能的;等待线程可能会看到“cam”指针的更新,而没有看到 cam->status 的初始化。实际上,在这种情况下和一般情况下,第二个 sn-p 是在自找麻烦。

    http://www.hpl.hp.com/personal/Hans_Boehm/c++mm/

    【讨论】:

    • 事实上,在第二个 sn-p 中,信号量本身可能会保护(或不保护)状态标志(尽管我无法想象一个好的锁定协议)。
    【解决方案4】:

    在您的第二次 sn-p 中,您多次获得锁定,从未释放它。

    一般情况下,您正在等待的状态可以完全由信号量表示,然后您就可以使用它。锁结构体积更小,检查/设置/释放所需的原子操作更少。

    否则,如果状态很复杂,并且代码的不同部分在同一变量的不同条件下等待(例如,这里需要 xx),请使用 cond_wait。

    【讨论】:

    • 第二个 sn-p 是正确的 - 唤醒总是由有人发出信号量引起的。
    • @Blaisorblade,唤醒是正确的,状态检查是no,因为它没有被看守。
    • 这与这个答案所描述的问题不同——“在你的第二个 sn-p 中,你得到了很多次锁,永远不会释放它”。我在上面的回答中提到了这个问题(虽然不是很具体)。但是在再次查看代码时,我意识到无法真正判断第二个 sn-p 是否正确,因为缺少太多细节(请参阅我对问题的评论)。但我的评论措辞不正确——我应该说“你提到的问题不存在,因为......”。
    猜你喜欢
    • 2018-02-05
    • 1970-01-01
    • 2017-08-24
    • 2010-09-16
    • 1970-01-01
    • 2012-09-01
    • 2013-05-30
    • 1970-01-01
    相关资源
    最近更新 更多