2009-06-16 96 views
6

调用pthread_cond_timedwait时是否有任何缺点,而没有首先锁定关联的互斥锁,并且在调用pthread_cond_signal时没有执行互斥锁?不锁定pthread_cond_timedwait和pthread_cond_signal的互斥锁(在Linux上)

在我来说真的是有没有条件来检查,我想非常类似于Java等待(长)行为和notify()。

根据该文件,可以有“不可预测的调度行为”。我不确定这意味着什么。

一个例子程序似乎没有先锁定互斥体做工精细。

+0

仅仅是明确的,你要的是等待高达N秒,除非你是早醒来? – 2009-06-16 18:36:33

+0

是的。大概的信号量是一个更好的交易。 – 2009-06-16 19:26:21

回答

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(),然后你根据等待是否()或信号()胜得到完全不同的行为:如果信号()在第一个钻进,那么你会即使您关心的信号已经发生,也要等待完全超时。这很少是条件变量的用户想要的,但对你来说可能没问题。也许这就是“不可预知的调度程序行为”的文档意味着什么 - 突然间,时间片对于程序的行为变得至关重要。

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

此方法应该仅由一个 线程,它是此 对象监视器的所有者被称为。

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

Java的同步{/} /等待/ notifty/notifyAll的行为类似于对pthread_mutex_lock /调用pthread_mutex_unlock /调用pthread_cond_wait /调用pthread_cond_signal /调用pthread_cond_broadcast,而不是由巧合。

+0

您可能希望修复到JavaDoc的链接...括号需要成为URL的一部分。 – 2009-06-16 19:47:12

+1

如果监视器没有被正确的线程所拥有,则抛出Java。 – Arkadiy 2009-06-16 20:39:30

+2

我认为它的意思是,当你修改条件,然后释放锁,另一个线程(称为B)可以抢互斥,检查车况,并认为它是信号,并继续该行为。与此同时,cond_signal()正在触发,另一个线程(称为A)将能够检查试图锁定互斥锁的条件块。当A最终确实抓住互斥锁时,它看到B已经重置了条件,并在cond_wait中再次返回到睡眠状态。如果所有的线程都是“相等的”,它是无害的,但理论上线程A可能会饿死。 – 2009-06-16 21:32:36

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

“不可预测的调度行为” 是指这一点。你不知道会发生什么。 也没有执行。它可以按预期工作。它可能会导致应用程序崩溃它可以很好地工作多年,然后竞争条件使你的应用程序变成猴子。它可能会陷入僵局。

基本上,如果任何文档显示任何未定义/不可预知的事情会发生,除非您做文档告诉您要做的事情,那么您最好这样做。其他的东西可能会炸毁你的脸。 (它不会炸毁,直到你把代码投入生产,只是为了更多地激怒你)至少这是我的经验

9

Butenhof出色的“使用POSIX线程编程”在第3.3章0.3。

基本上,信令condvar不锁定的互斥为潜在性能优化:如果信令螺纹具有锁定互斥,那么线程醒来上condvar必须立即互斥阻止信令线程已锁定即使信号线程没有修改等待线程将使用的任何数据。

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

0

只要有可能,应在互斥体外发出信号。互斥编程是并发编程中的必要罪恶。它们的使用导致竞争,从而使系统获得使用多个处理器所能获得的最大性能。

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

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

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

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

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

底线是,的pthread_cond_broadcast的位置并不用于控制执行顺序的适当的工具。它不会使执行顺序可预测,因为程序突然在每个平台上具有完全相同的可重复行为。