没有为pthread_cond_timedwait和pthread_cond_signal锁定互斥锁(在Linux上)

时间:2009-06-16 18:31:11

标签: c++ c multithreading pthreads mutex

调用pthread_cond_timedwait是否有任何缺点,而不首先锁定关联的互斥锁,并且在调用pthread_cond_signal时也不会使用互斥锁?

在我的情况下,确实没有条件要检查,我想要一个非常类似于Java wait(long)和notify()的行为。

根据文档,可能存在“不可预测的调度行为”。我不确定这意味着什么。

示例程序似乎可以正常工作,而无需先锁定互斥锁。

6 个答案:

答案 0 :(得分:11)

第一个不行:

  

pthread_cond_timedwait()和   pthread_cond_wait()函数应该   阻止条件变量。他们   应该用互斥锁锁定   调用线程或未定义   行为结果。

http://opengroup.org/onlinepubs/009695399/functions/pthread_cond_timedwait.html

原因是实现可能希望依赖锁定的互斥锁,以便安全地将您添加到服务器列表中。它可能想要在没有先检查它的情况下释放互斥锁。

第二个是令人不安的:

  

如果可预测的调度行为是   必需,然后该互斥锁被锁定   线程调用   pthread_cond_signal()或   pthread_cond_broadcast()

http://www.opengroup.org/onlinepubs/007908775/xsh/pthread_cond_signal.html

在我的头顶上,我不确定具体的竞争条件是什么会扰乱调度程序行为,如果你发出信号而没有锁定。所以我不知道未定义的调度程序行为有多糟糕:例如,对于广播,服务员只是没有按优先级顺序获取锁(或者您的特定调度程序通常表现)。或者也许服务员会“迷失”。

通常情况下,对于条件变量,您需要设置条件(至少是标志)和信号,而不仅仅是信号,为此您需要使用互斥锁。原因是否则,如果你与另一个调用wait()的线程并发,那么你会根据wait()或signal()是否获胜而获得完全不同的行为:如果signal()先偷偷摸摸,那么你将会即使您关心的信号已经发生,也要等待完全超时。这很少是条件变量的用户想要的,但可能对你没问题。也许这就是文档所说的“不可预测的调度程序行为” - 突然间,时间片对于程序的行为变得至关重要。

顺便说一下,在Java中你必须拥有锁才能通知()或notifyAll():

  

此方法只能由a调用   线程是这个的拥有者   对象的监视器。

http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#notify()

Java synchronized {/} / wait / notifty / notifyAll行为类似于pthread_mutex_lock / pthread_mutex_unlock / pthread_cond_wait / pthread_cond_signal / pthread_cond_broadcast,而不是巧合。

答案 1 :(得分:9)

Butenhof出色的“使用POSIX线程编程”在第3.3.3章末尾对此进行了讨论。

基本上,在不锁定互斥锁的情况下发信号通知condvar是潜在的性能优化:如果信令线程已锁定互斥锁,则在condvar上唤醒的线程必须立即阻止互斥锁上的互斥锁即使信令线程没有修改等待线程将使用的任何数据,信令线程也已锁定。

提到“不可预测的调度程序行为”的原因是,如果您有一个高优先级线程在condvar上等待(另一个线程将发出信号并唤醒高优先级线程),那么任何其他优先级较低的线程都可以来锁定互斥锁,以便在发出condvar信号并唤醒高优先级线程时,它必须等待优先级较低的线程释放互斥锁。如果在发信号时锁定互斥锁,那么优先级较高的线程将在优先级较低的线程之前在互斥锁上进行调度:基本上你知道当你“唤醒”高优先级线程时,它会在调度程序中立即唤醒允许它(当然,您可能必须在发出高优先级线程之前等待互斥锁,但这是一个不同的问题)。

答案 2 :(得分:3)

等待条件变量与互斥锁配对的点是原子进入等待并释放锁定,即允许其他线程修改受保护状态,然后再自动接收状态变化通知获得锁。您描述的内容可以使用许多其他方法来完成,例如管道,套接字,信号,或者 - 可能是最合适的 - 信号量

答案 3 :(得分:1)

我认为这应该有效(注意未经测试的代码):

// initialize a semaphore
sem_t sem;
sem_init(&sem,
    0, // not shared
    0  // initial value of 0
    );


// thread A
struct timespec tm;
struct timeb    tp;

const long sec      = msecs / 1000;
const long millisec = msecs % 1000;

ftime(&tp);
tp.time += sec;
tp.millitm += millisec;
if(tp.millitm > 999) {
    tp.millitm -= 1000;
    tp.time++;
}
tm.tv_sec  = tp.time;
tm.tv_nsec = tp.millitm * 1000000;

// wait until timeout or woken up
errno = 0;
while((sem_timedwait(&sem, &tm)) == -1 && errno == EINTR) {
    continue;
}

return errno == ETIMEDOUT; // returns true if a timeout occured


// thread B
sem_post(&sem); // wake up Thread A early

答案 4 :(得分:0)

应尽可能在互斥锁之外发出信号。互斥程序是并发编程中必不可少的恶魔。它们的使用导致了争用,这种争用剥夺了系统在使用多个处理器时可以获得的最大性能。

互斥锁的目的是保护对程序中某些共享变量的访问,以便它们以原子方式运行。当在互斥锁内部完成信令操作时,它会导致数百个不相关的机器周期包含在互斥锁中,这与保护共享数据无关。它可能会从用户空间一直调用到内核中。

标准中关于“可预测的调度程序行为”的注释完全是假的。

当我们希望机器以可预测的,明确定义的顺序执行语句时,该工具就是单个执行线程中语句的排序:S1 ; S2。声明S1已在S2之前“安排”。

当我们意识到某些操作是独立的并且它们的调度顺序不重要时,我们使用线程,并且要实现性能优势,例如对实时事件的更及时响应或在多个处理器上进行计算。

当调度订单在多个线程中变得很重要时,这属于一个名为 priority 的概念。优先级解决了当N个语句中的任何一个可能被安排执行时首先发生的事情。在多线程下排序的另一个工具是排队。事件由一个或多个线程放入队列,单个服务线程按队列顺序处理事件。

最重要的是,pthread_cond_broadcast的展示位置不是控制执行订单的合适工具。在程序突然在每个平台上具有完全相同,可重现的行为的意义上,它不会使执行顺序可预测。

答案 5 :(得分:-1)

“不可预测的调度行为”就是这个意思。你不知道会发生什么。 实施也没有。它可以按预期工作。它可能会导致您的应用崩溃它可以正常工作多年,然后竞争条件使你的应用程序成为猴子。它可能会陷入僵局。

基本上,如果任何文档提示任何未定义/不可预测的事情都可能发生,除非你按照文档告诉你做的那样,你最好这样做。其他东西可能会在你的脸上爆炸。 (在你将代码投入生产之前不会爆炸,只是为了让你更烦恼。至少这是我的经验)