214

Seeing various locking related question and (almost) always finding the 'loop because of spurious wakeups' terms1 I wonder, has anyone experienced such kind of a wakeup (assuming a decent hardware/software environment for example)?

I know the term 'spurious' means no apparent reason but what can be the reasons for such kind of an event?

(1 Note: I'm not questioning the looping practice.)

Edit: A helper question (for those who like code samples):

If I have the following program, and I run it:

public class Spurious {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition cond = lock.newCondition();
        lock.lock();
        try {
            try {
                cond.await();
                System.out.println("Spurious wakeup!");
            } catch (InterruptedException ex) {
                System.out.println("Just a regular interrupt.");
            }
        } finally {
            lock.unlock();
        }
    }
}

What can I do to wake this await up spuriously without waiting forever for a random event?

4

7 回答 7

210

维基百科关于虚假唤醒的文章有这个花絮:

pthread_cond_wait()Linux中的功能是使用futex系统调用来实现的。EINTR当进程收到信号时,Linux 上的每个阻塞系统调用都会突然返回。...pthread_cond_wait()无法重新开始等待,因为它可能会在futex系统调用之外的一小段时间内错过真正的唤醒。这种竞争条件只能通过调用者检查不变量来避免。因此,POSIX 信号将产生虚假唤醒。

简介: 如果一个 Linux 进程收到信号,它的等待线程将享受一个不错的、热的虚假唤醒

我买它。这比通常给出的通常含糊的“这是为了性能”的原因更容易下咽。

于 2009-06-27T00:28:52.950 回答
23

我有一个表现出这种行为的生产系统。线程等待队列中有消息的信号。在繁忙时段,高达 20% 的唤醒是虚假的(即,当它唤醒时,队列中没有任何内容)。该线程是消息的唯一消费者。它在 Linux SLES-10 8 处理器机器上运行,并使用 GCC 4.1.2 构建。消息来自外部源并被异步处理,因为如果我的系统读取它们的速度不够快,就会出现问题。

于 2010-09-29T00:43:29.203 回答
17

回答标题中的问题 -是的!它确实发生了。尽管Wiki 文章提到了很多关于虚假唤醒的内容,但我遇到的同样情况的一个很好的解释如下 -

想一想……就像任何代码一样,线程调度程序可能会由于底层硬件/软件中发生异常情况而暂时中断。当然,应该注意这种情况尽可能少发生,但是由于没有 100% 健壮的软件这样的事情,因此假设这可能发生并注意优雅恢复以防调度程序检测到这种情况(例如通过观察丢失的心跳)。

现在,考虑到在停电期间它可能会错过一些旨在通知等待线程的信号,调度程序如何恢复?如果调度程序什么都不做,则提到的“不幸”线程将挂起,永远等待——为避免这种情况,调度程序将简单地向所有等待的线程发送信号。

这使得有必要建立一个“合同”,可以无理由地通知等待线程。确切地说,会有一个原因 - 调度程序中断 - 但由于线程被设计(有充分理由)忽略调度程序内部实现细节,这个原因可能更好地呈现为“虚假”。

我正在从Source阅读这个答案,发现它足够合理。另请阅读

Java 中的虚假唤醒以及如何避免它们

PS:以上链接指向我的个人博客,其中包含有关虚假唤醒的更多详细信息。

于 2014-08-02T15:56:45.580 回答
9

Cameron Purdy 不久前写了一篇关于被虚假唤醒问题打击的博客文章。所以是的,它发生了

我猜它在规范中(作为一种可能性)是因为 Java 部署的某些平台的限制?虽然我可能错了!

于 2009-06-26T19:03:05.043 回答
8

Just to add this. Yes it happens and I spent three days searching for the cause of a multi-threading problem on a 24 core machine (JDK 6). 4 of 10 executions experienced that without any pattern. This never happened on 2 core or 8 cores.

Studied some online material and this is not a Java problem but a general rare but expected behavior.

于 2011-05-05T12:42:45.920 回答
1

回答OP的问题

我能做些什么来虚假地唤醒这个等待而不是永远等待随机事件?

没有任何虚假的唤醒可以唤醒这个等待线程!

无论虚假唤醒是否可以在特定平台上发生,在 OP 的片段中,绝对不可能返回Condition.await()并看到“虚假唤醒!”这一行。在输出流中。

除非您使用非常奇特的Java 类库

这是因为标准,OpenJDKReentrantLock' 方法newCondition()返回的接口AbstractQueuedSynchronizer的实现Condition,嵌套ConditionObject(顺便说一下,它是该Condition类库中接口的唯一实现),并且ConditionObject' 方法await()本身检查条件是否不保持,并且没有任何虚假唤醒可以强制此方法错误地返回。

顺便说一句,您可以自己检查一下,因为一旦AbstractQueuedSynchronizer涉及基于 - 的实现,就很容易模拟虚假唤醒。 AbstractQueuedSynchronizer使用低级LockSupport'sparkunpark方法,如果您LockSupport.unpark在等待 on 的线程上调用Condition,则无法将此操作与虚假唤醒区分开来。

稍微重构 OP 的片段,

public class Spurious {

    private static class AwaitingThread extends Thread {

        @Override
        public void run() {
            Lock lock = new ReentrantLock();
            Condition cond = lock.newCondition();
            lock.lock();
            try {
                try {
                    cond.await();
                    System.out.println("Spurious wakeup!");
                } catch (InterruptedException ex) {
                    System.out.println("Just a regular interrupt.");
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private static final int AMOUNT_OF_SPURIOUS_WAKEUPS = 10;

    public static void main(String[] args) throws InterruptedException {
        Thread awaitingThread = new AwaitingThread();
        awaitingThread.start();
        Thread.sleep(10000);
        for(int i =0 ; i < AMOUNT_OF_SPURIOUS_WAKEUPS; i++)
            LockSupport.unpark(awaitingThread);
        Thread.sleep(10000);
        if (awaitingThread.isAlive())
            System.out.println("Even after " + AMOUNT_OF_SPURIOUS_WAKEUPS + " \"spurious wakeups\" the Condition is stil awaiting");
        else
            System.out.println("You are using very unusual implementation of java.util.concurrent.locks.Condition");
    }
}

,并且无论 unparking(main) 线程多么努力地尝试唤醒等待线程,Condition.await()在这种情况下该方法永远不会返回。

在接口的javadocCondition中讨论了等待方法的虚假唤醒。虽然是这么说的,Condition

在等待条件时,允许发生虚假唤醒

然后

建议应用程序程序员总是假设它们可以发生,因此总是在循环中等待。

但它后来补充说

一个实现是免费的,以消除虚假唤醒的可能性

andAbstractQueuedSynchronizerCondition接口实现正是这样做的——消除了任何虚假唤醒的可能性

这肯定适用于 otherConditionObject的等待方法。

所以,结论是:

我们应该始终Condition.await在循环中调用并检查条件是否不成立,但是对于标准的 OpenJDK,Java 类库永远不会发生。除非再次使用非常不寻常的 Java 类库(这一定是非常非常不寻常的,因为另一个著名的非 OpenJDK Java 类库,目前几乎绝迹GNU ClasspathApache Harmony,似乎与Condition接口的标准实现相同)

于 2020-01-23T22:30:51.330 回答
0

https://stackoverflow.com/a/1461956/14731很好地解释了为什么即使底层操作系统没有触发虚假唤醒,您也需要防范它们。有趣的是,这种解释适用于多种编程语言,包括 Java。

于 2018-07-03T17:52:53.947 回答