6

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

就我而言,确实没有条件检查,我想要一个与 Java wait(long) 和 notify() 非常相似的行为。

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

一个示例程序似乎可以在不首先锁定互斥锁的情况下正常工作。

4

6 回答 6

12

第一个不行:

pthread_cond_timedwait()and 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():

此方法只能由作为该对象监视器所有者的线程调用。

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

Java 同步的 {/}/wait/notifty/notifyAll 行为类似于 pthread_mutex_lock/pthread_mutex_unlock/pthread_cond_wait/pthread_cond_signal/pthread_cond_broadcast,并非巧合。

于 2009-06-16T18:42:22.090 回答
10

Butenhof 出色的“使用 POSIX 线程编程”在第 3.3.3 章的末尾讨论了这一点。

基本上,在不锁定互斥锁的情况下向 condvar 发送信号是一种潜在的性能优化:如果信号线程锁定了互斥锁,那么在 condvar 上唤醒的线程必须立即阻塞信号线程已锁定的互斥锁,即使信号线程是不修改等待线程将使用的任何数据。

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

于 2010-02-19T17:51:46.243 回答
3

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

于 2009-06-16T18:43:57.080 回答
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
于 2009-06-16T18:44:43.197 回答
0

条件应尽可能在互斥体之外发出信号。互斥锁是并发编程中必不可少的邪恶。它们的使用会导致争用,从而使系统无法从使用多个处理器中获得最大性能。

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

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

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

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

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

底线是,放置pthread_cond_broadcast不是控制执行顺序的合适工具。它不会使执行顺序可预测,因为程序突然在每个平台上都具有完全相同的、可重现的行为。

于 2013-05-29T17:21:32.727 回答
-1

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

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

于 2009-06-16T20:02:32.417 回答