这个问题主要来源与我们在使用pthread条件变量wait函数的时候总是使用while去做判断而不是使用if,因为等待在条件变量上的线程被唤醒有可能不是因为条件满足而是由于虚假唤醒(Spurious wakeups)
That's called spurious wakeup and is explicitly allowed by POSIX. Essentially, return from 'wait' only indicates that the shared data might have changed, so that data must be evaluated again.
虚假唤醒在POSIX标准里是默认允许的,所以wait返回只是代表共享数据有可能被改变,因此必须要重新判断.
那么什么时候会出现虚假唤醒呢?
在多核处理器下,pthread_cond_signal可能会激活多于一个线程(阻塞在条件变量上的线程)。
结果是,当一个线程调用pthread_cond_signal()后,多个调用pthread_cond_wait()或pthread_cond_timedwait()的线程返回。
那么为什么这个没有被修复呢?
原因在参考文献中,简单的说有两点:
1 The first reason is that nobody wants to fix it.需要使用while判断条件也不仅仅是因为虚假唤醒,还有在前一篇提到的几个原因.
2 The second reason is that fixing this is supposed to be hard.虽然虚假唤醒在pthread_cond_wait函数中可以解决,为了发生概率很低的情况而降低边缘条件(fringe condition)效率是不值得的,纠正这个问题会降低对所有基于它的所有更高级的同步操作的并发度。所以pthread_cond_wait的实现上没有去解决它。
在linux中,pthread_cond_wait底层是futex系统调用。在linux中,任何慢速的阻塞的系统调用当接收到信号的时候,就会返回-1,并且设置errno为EINTR。在系统调用返回前,用户程序注册的信号处理函数会被调用处理。
注:什么有样的系统调用会出现接收信号后发挥EINTR呢?
慢速阻塞的系统调用,有可能会永远阻塞下去的那种。当接收到信号的时候,认为是一个返回并执行其他代码的一个时机。
信号的处理也不简单,因为有些慢系统调用被信号中断后是会自动重启的,所以我们通常需要用siginterrupt(signo, 1)来关闭重启或者在用sigaction安装信号处理函数的时候取消SA_RESTART标志,之后就可以通过判断信号的返回值是否是-1和errno是否为EINTR来判断是否有信号抵达。
如果关闭了SA_RESTART的一些使用慢速系统调用的应用,一般都采用while()循环,检测到EINTR后就重新调用。
while(1)
{
int ret = syscall();
if(ret<0 && errno==EINTR)
continue;
else
break;
}
但是,对于futex这种方法不行,因为futex结束后,再重新运行的过程中,会出现一个时间窗口,其他线程可能会在这个时间窗口中进行pthread_cond_signal,这样,再进行pthread_cond_wait的时候就丢失了一次条件变量的变化。解决方法就是在pthread_cond_wait前检查条件变量,也就是
pthread_mutex_lock();
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
pthread_cond_broadcast
实际上,不仅仅信号会导致假唤醒,pthread_cond_broadcast也会导致假唤醒。加入条件变量上有多个线程在等待,pthread_cond_broadcast会唤醒所有的等待线程,而pthread_cond_signal只会唤醒其中一个等待线程。这样,pthread_cond_broadcast的情况也许要在pthread_cond_wait前使用while循环来检查条件变量。
http://blog.vladimirprus.com/2005/07/spurious-wakeups.html
http://www.cnblogs.com/leaven/archive/2010/06/03/1750973.html