不锁定互斥量进行pthread_cond_timedwait和pthread_cond_signal操作(在Linux上)

6

在调用pthread_cond_timedwait之前没有获取相关互斥锁,同时在调用pthread_cond_signal时也没有获取互斥锁,这样做是否有不利影响呢?

在我的情况下,实际上没有任何条件需要检查,我想要的行为与Java wait(long)和notify()非常相似。

根据文档,可能会出现“不可预测的调度行为”,我不确定这是什么意思。

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


只是为了明确,你想要等待最多N秒,除非你被提前唤醒? - Evan Teran
可能信号量更好一些。 - Sid Datta
6个回答

12

第一个不行:

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()先 sneaks in,则即使您关心的信号已经发生,您也会等待完整的超时时间。这很少是条件变量的用户想要的,但对您来说可能没问题。也许这就是文档所说的“不可预测的调度程序行为” - 突然之间,时间片变得对程序的行为至关重要。顺便说一句,在Java中,您必须拥有锁才能notify()或notifyAll():此方法只应由拥有此对象监视器的线程调用。

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,这不是巧合。

你可能想修复JavaDoc的链接...括号需要作为URL的一部分。 - Chris Arguin
2
我认为它的意思是,当您修改条件并释放锁时,另一个线程(称其为B)可以获取互斥锁,检查条件并发现它已被标记,然后进行操作。同时,cond_signal()正在触发,原本要检查条件的另一个线程(称其为A)正在尝试锁定互斥锁时会被阻塞。当A最终获取互斥锁时,它发现B已经重置了条件并再次在cond_wait中休眠。如果所有线程都是“平等”的,则没有问题,但理论上线程A可能会被饿死。 - Greg Rogers

10

在Butenhof的优秀著作《Programming with POSIX Threads》的第3.3.3章节末尾详细讨论了这个问题。

基本上,在没有锁定互斥量的情况下发出条件变量的信号是一种潜在的性能优化:如果发出信号的线程已经锁定了互斥量,那么等待条件变量被唤醒的线程必须立即在该互斥量上阻塞,即使发出信号的线程没有修改任何等待线程将使用的数据。

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


对于那些难以理解“为什么在条件变量信号时应该锁定互斥量”的问题的人来说,如果有一个好的解释,那就加1分吧! - R.. GitHub STOP HELPING ICE
在没有互斥锁的情况下发出condvar信号不仅是一种潜在的优化,而且可以减少锁定中数百个不必要的机器周期。 - Kaz
@Kaz:是的,你正在从锁中删除机器周期,并在微观层面上进行了优化,但如果你遇到了优先级反转问题,而你的高优先级线程正在等待低优先级线程,那么你可能有一个次优程序。 - TheJuice

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

+1,但要注意sem_timed_wait是可选的。我的意思是,它比信号量更加可选,而信号量本身也是可选的,但如果没有它们,使用pthread就没有太多意义了... - Steve Jessop

0

条件应该在可能的情况下在互斥锁之外被触发。互斥锁是并发编程中必不可少的恶魔。它们的使用会导致竞争,从而剥夺系统从多个处理器使用中获得的最大性能。

互斥锁的目的是保护程序中的某些共享变量,使它们表现出原子性。当信号操作在互斥锁内部执行时,它会导致数百个与保护共享数据无关的机器周期被包含在互斥锁中。它可能会从用户空间调用到内核。

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

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

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

在多线程环境下,当调度订单变得重要时,这就涉及到一个叫做优先级的概念。优先级决定了在可能有N个语句可以被调度执行时,哪个语句会先执行。另一个用于多线程排序的工具是队列。事件由一个或多个线程放入队列中,然后单个服务线程按照队列顺序处理这些事件。

总之,将pthread_cond_broadcast放置在控制执行顺序上并不是一个合适的工具。它不能使程序在每个平台上都具有相同、可重现的行为,因此执行顺序也无法预测。


-1
"不可预测的调度行为" 就是这个意思,你不知道会发生什么。 实现也一样。它可能正常工作,也可能崩溃你的应用程序。它可能多年正常运行,然后一个竞态条件让你的应用程序变得混乱。它可能死锁。
基本上,如果任何文档都暗示除非你按照文档要求做,否则可能会发生未定义/不可预测的事情,那你最好这么做。否则,东西可能在你面前爆炸。(并且它不会爆炸,直到你把代码投入生产中,更加恼人。至少这是我的经验)

2
我不同意。 “不可预测的调度程序行为”的含义不明确,但我相当确定意图并不是暗示“未定义的行为”,否则他们会像在pthread_cond_wait中一样明确说明。 - Steve Jessop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接