在调用pthread_cond_timedwait之前没有获取相关互斥锁,同时在调用pthread_cond_signal时也没有获取互斥锁,这样做是否有不利影响呢?
在我的情况下,实际上没有任何条件需要检查,我想要的行为与Java wait(long)和notify()非常相似。
根据文档,可能会出现“不可预测的调度行为”,我不确定这是什么意思。
一个示例程序似乎可以在不先锁定互斥锁的情况下正常工作。
在调用pthread_cond_timedwait之前没有获取相关互斥锁,同时在调用pthread_cond_signal时也没有获取互斥锁,这样做是否有不利影响呢?
在我的情况下,实际上没有任何条件需要检查,我想要的行为与Java wait(long)和notify()非常相似。
根据文档,可能会出现“不可预测的调度行为”,我不确定这是什么意思。
一个示例程序似乎可以在不先锁定互斥锁的情况下正常工作。
第一个不行:
pthread_cond_timedwait()
和pthread_cond_wait()
函数将会在条件变量上阻塞。它们 必须在调用线程锁定互斥体或者导致未定义的行为。
http://opengroup.org/onlinepubs/009695399/functions/pthread_cond_timedwait.html
实现可能希望依赖于互斥锁被锁定,以便安全地将您添加到等待列表中。它可能希望在没有先检查是否保持互斥锁的情况下释放互斥锁。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,这不是巧合。在Butenhof的优秀著作《Programming with POSIX Threads》的第3.3.3章节末尾详细讨论了这个问题。
基本上,在没有锁定互斥量的情况下发出条件变量的信号是一种潜在的性能优化:如果发出信号的线程已经锁定了互斥量,那么等待条件变量被唤醒的线程必须立即在该互斥量上阻塞,即使发出信号的线程没有修改任何等待线程将使用的数据。
提到“不可预测的调度器行为”的原因是,如果有一个高优先级的线程正在等待条件变量(另一个线程将要发出信号并唤醒高优先级线程),那么任何低优先级的其他线程都可以来锁定互斥量,以至于当条件变量被发出信号并唤醒高优先级线程时,它必须等待低优先级线程释放互斥量。如果互斥量在发出信号时被锁定,则在较低优先级线程之前,高优先级线程将在互斥量上调度:基本上,当您“唤醒”高优先级线程时,您知道它会在调度器允许它时立即唤醒(当然,您可能必须在发出信号之前等待互斥量,但这是另一个问题)。
我认为这应该可以工作(请注意,代码未经测试):
// 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
条件应该在可能的情况下在互斥锁之外被触发。互斥锁是并发编程中必不可少的恶魔。它们的使用会导致竞争,从而剥夺系统从多个处理器使用中获得的最大性能。
互斥锁的目的是保护程序中的某些共享变量,使它们表现出原子性。当信号操作在互斥锁内部执行时,它会导致数百个与保护共享数据无关的机器周期被包含在互斥锁中。它可能会从用户空间调用到内核。
标准中关于“可预测的调度程序行为”的注释完全是虚假的。
当我们希望机器按照可预测、明确定义的顺序执行语句时,这种工具就是单个执行线程中语句的排序:S1 ; S2
。语句S1
在语句S2
之前“调度”。
当我们意识到一些操作是独立的,它们的调度顺序并不重要,并且有性能优势可以实现,例如更及时地响应实时事件或在多个处理器上进行计算时,我们使用线程。
在多线程环境下,当调度订单变得重要时,这就涉及到一个叫做优先级的概念。优先级决定了在可能有N个语句可以被调度执行时,哪个语句会先执行。另一个用于多线程排序的工具是队列。事件由一个或多个线程放入队列中,然后单个服务线程按照队列顺序处理这些事件。
总之,将pthread_cond_broadcast
放置在控制执行顺序上并不是一个合适的工具。它不能使程序在每个平台上都具有相同、可重现的行为,因此执行顺序也无法预测。