虚假唤醒(spurious wakeup)

1.Linux对虚假唤醒的说明

On a multi-processor, it may be impossible for an implementation of pthread_cond_signal() to avoid the unblocking of more than one thread blocked on a condition variable.
The effect is that more than one thread can return from its call to pthread_cond_wait() or pthread_cond_timedwait() as a result of one call to pthread_cond_signal(). This effect is called “spurious wakeup”. Note that the situation is self-correcting in that the number of threads that are so awakened is finite; for example, the next thread to call pthread_cond_wait() after the sequence of events above blocks.
While this problem could be resolved, the loss of efficiency for a fringe condition that occurs only rarely is unacceptable, especially given that one has to check the predicate associated with a condition variable anyway. Correcting this problem would unnecessarily reduce the degree of concurrency in this basic building block for all higher-level synchronization operations.

  • 在多核处理器下,pthread_cond_signal可能会激活多于一个线程(阻塞在条件变量上的线程)。结果是,当一个线程调用pthread_cond_signal()后,多个调用pthread_cond_wait()或pthread_cond_timedwait()的线程返回。这种效应成为”虚假唤醒”(spurious wakeup)。
    虽然虚假唤醒在pthread_cond_wait函数中可以解决,为了发生概率很低的情况而降低边缘条件(fringe condition)效率是不值得的,纠正这个问题会降低对所有基于它的所有更高级的同步操作的并发度。所以pthread_cond_wait的实现上没有去解它。

  • 通常的解决方法是将if改为while

static void *thread_func(void *arg)
{
    while (1) {
      pthread_mutex_lock(&mtx);           //这个mutex主要是用来保证pthread_cond_wait的并发性
      while (msg_list.empty())   {     //pthread_cond_wait里的线程可能会被意外唤醒(虚假唤醒),如果这个时候,则不是我们想要的情况。这个时候,应该让线程继续进入pthread_cond_wait
          pthread_cond_wait(&cond, &mtx);
      }
      msg = msg_list.pop();
      pthread_mutex_unlock(&mtx);             //临界区数据操作完毕,释放互斥锁
       // handle msg
    }
    return 0;
}

2.其他对虚假唤醒的说明

An added benefit of allowing spurious wakeups is that applications are forced to code a predicate-testing-loop around the condition wait. This also makes the application tolerate superfluous condition broadcasts or signals on the same condition variable that may be coded in some other part of the application. The resulting applications are thus more robust. Therefore, IEEE Std 1003.1-2001 explicitly documents that spurious wakeups may occur.

  • 在linux对条件变量的描述认为spurious wakeup是允许的,添加while检查的做法被认为是增加了程序的健壮性。

but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations. The race conditions that cause spurious wakeups should be considered rare.

  • 在POSIX Threads中:
    David R. Butenhof 认为多核系统中 条件竞争(race condition)导致了虚假唤醒的发生,并且认为完全消除虚假唤醒本质上会降低了条件变量的操作性能,因为虚假唤醒发生的概率发生很小。

  • Effective java 曾经提到Item 50: Never invoke wait outside a loop.

3.linux中为什么要设置虚假唤醒

在linux中,pthread_cond_wait底层是futex系统调用。在linux中,任何慢速的阻塞的系统调用当接收到信号的时候,就会返回-1,并且设置errno为EINTR。在系统调用返回前,用户程序注册的信号处理函数会被调用处理。

  • 注:什么有样的系统调用会出现接收信号后发挥EINTR呢?
    慢速阻塞的系统调用,有可能会永远阻塞下去的那种。当接收到信号的时候,认为是一个返回并执行其他代码的一个时机。
    信号的处理也不简单,因为有些慢系统调用被信号中断后是会自动重启的,所以我们通常需要用siginterrupt(signo, 1)来关闭重启或者在用sigaction安装信号处理函数的时候取消SA_RESTART标志,之后就可以通过判断信号的返回值是否是-1和errno是否为EINTR来判断是否有信号抵达。
    如果关闭了SA_RESTART的一些使用慢速系统调用的应用,一般都采用while()循环,检测到EINTR后就重新调用。

参考

  • 1)对条件变量(condition variable)的讨论

你可能感兴趣的:(虚假唤醒(spurious wakeup))