Случаются ли ложные пробуждения в Java на самом деле?


208

Рассматривая различные вопросы, связанные с блокировкой, и (почти) всегда находя термин «цикл из-за ложных пробуждений» 1 Интересно, кто-нибудь испытывал такое пробуждение (например, при условии достойной аппаратной / программной среды)?

Я знаю, что термин «ложный» означает отсутствие очевидной причины, но каковы могут быть причины такого рода события?

( 1 Примечание: я не ставлю под сомнение практику зацикливания.)

Изменить: вспомогательный вопрос (для тех, кто любит примеры кода):

Если у меня есть следующая программа, и я запускаю ее:

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();
        }
    }
}

Что я могу сделать, чтобы внезапно разбудить это await, не ожидая навсегда случайного события?


1
Для JVM, которые работают в системах POSIX и используют pthread_cond_wait()реальный вопрос: «Почему pthread_cond_wait имеет ложные пробуждения?» ,
Поток

Ответы:


204

В статье Википедии о ложных пробуждениях есть этот кусочек:

pthread_cond_wait()Функция в Linux осуществляется с помощью futexсистемного вызова. Каждый блокирующий системный вызов в Linux внезапно завершается, EINTRкогда процесс получает сигнал. ... pthread_cond_wait()не может перезапустить ожидание, потому что оно может пропустить реальное пробуждение за то короткое время, которое было за пределами futexсистемного вызова. Этого состояния гонки можно избежать только путем проверки вызывающим абонентом инварианта. Поэтому сигнал POSIX будет генерировать ложное пробуждение.

Описание : Если процесс Linux сигнализирует, его ожидающие потоки будут наслаждаться приятным горячим ложным пробуждением .

Я покупаю это. Эту таблетку легче проглотить, чем часто приводимую расплывчатую причину «это для эффективности».


13
Лучшее объяснение здесь: stackoverflow.com/questions/1461913/…
Гили

3
Эта разблокировка EINTR верна для всех блокирующих системных вызовов в системах, производных от Unix. Это сделало ядро ​​намного проще, но прикладные программисты взяли на себя это бремя.
Тим Уиллискрофт

2
Я думал, что pthread_cond_wait () и друзья не могут вернуть EINTR, но вернуть ноль, если внезапно проснулся? From: pubs.opengroup.org/onlinepubs/7908799/xsh/… "Эти функции не будут возвращать код ошибки [EINTR]."
грязный

2
@jgubby Это верно. Основной futex()вызов возвращается EINTR, но это возвращаемое значение не поднимается до следующего уровня. Поэтому вызывающий pthread должен проверить наличие инварианта. Они говорят, что при pthread_cond_wait()возврате вы должны снова проверить условие цикла (инвариант), потому что ожидание могло быть внезапно пробуждено. Получение сигнала во время системного вызова - одна из возможных причин, но не единственная.
Джон Кугельман

1
Предположительно, pthreadбиблиотека могла бы предоставить свой собственный инвариант и собственную логику проверки, чтобы устранить ложные пробуждения, вместо того, чтобы передавать эту ответственность пользователю. Это (предположительно) окажет заявленное влияние на производительность.

22

У меня есть производственная система, которая демонстрирует это поведение. Поток ожидает сигнала о том, что в очереди есть сообщение. В периоды занятости до 20% пробуждений являются ложными (т.е. когда они просыпаются, в очереди ничего нет). Эта тема является единственным потребителем сообщений. Он работает на 8-процессорной коробке Linux SLES-10 и построен на GCC 4.1.2. Сообщения приходят из внешнего источника и обрабатываются асинхронно, потому что возникают проблемы, если моя система не читает их достаточно быстро.


15

Ответить на вопрос в титуле - да! это действительно случается. Хотя статья Wiki упоминает много о ложных пробуждениях, хорошее объяснение того же, с чем я столкнулся, следующее:

Подумайте об этом ... как и любой код, планировщик потоков может временно отключиться из-за чего-то ненормального, происходящего в базовом аппаратном / программном обеспечении. Конечно, следует позаботиться о том, чтобы это происходило как можно реже, но поскольку не существует такого понятия, как 100% надежное программное обеспечение, разумно предположить, что это может произойти, и позаботиться о постепенном восстановлении в случае, если планировщик обнаружит это (например, наблюдая отсутствующие сердцебиения).

Теперь, как планировщик мог восстановиться, учитывая, что во время отключения он мог пропустить некоторые сигналы, предназначенные для уведомления ожидающих потоков? Если планировщик ничего не делает, упомянутые «неудачные» потоки будут просто зависать, ожидая вечно - чтобы избежать этого, планировщик просто отправит сигнал всем ожидающим потокам.

Это делает необходимым заключение «контракта» о том, что ожидающий поток может быть уведомлен без причины. Чтобы быть точным, была бы причина - отключение планировщика - но поскольку поток спроектирован (по уважительной причине), чтобы не обращать внимания на внутренние детали реализации планировщика, эту причину, вероятно, лучше представить как «ложную».

Я читал этот ответ из источника и нашел его достаточно разумным. Также прочитайте

Ложные пробуждения в Java и как их избежать .

PS: Выше ссылка на мой личный блог, в котором есть дополнительная информация о ложных пробуждениях.


9

Некоторое время назад Кэмерон Пурди написала в своем блоге сообщение о проблеме ложного пробуждения. Так что да, так бывает

Я предполагаю, что это в спецификации (как возможность) из-за ограничений некоторых платформ, на которых развернута Java? хотя я могу ошибаться!


Я прочитал этот пост и подал мне идею о проведении модульных тестов для проверки соответствия одного приложения парадигме циклического ожидания путем его случайного / детерминированного пробуждения. Или это уже где-то доступно?
akarnokd

Другой вопрос к SO: «Существует ли строгая виртуальная машина, которую можно использовать для тестирования?». Я хотел бы видеть один со строгой локальной памятью потока - я не думаю, что они еще существуют
oxbow_lakes

8

Просто чтобы добавить это. Да, это случилось, и я потратил три дня на поиск причины многопоточности на 24-ядерном компьютере (JDK 6). 4 из 10 казней пережили это без какого-либо паттерна. Это никогда не случалось на 2 или 8 ядер.

Изучил некоторые онлайн-материалы, и это не проблема Java, а общее редкое, но ожидаемое поведение.


Здравствуйте, ReneS, вы разрабатывали (и работали) там приложение? Имеет ли (сделал) ли он метод wait (), вызывающий при проверке цикла внешнее условие, как это предлагается в документе java doc docs.oracle.com/javase/6/docs/api/java/lang/… ?
Gumkins

Я написал об этом, и да, решение - это цикл while с проверкой состояния. Моя ошибка заключалась в пропущенном цикле ... но я узнал об этих пробуждениях ... никогда не на двух ядрах, часто на 24core blog.xceptance.com/2011/05/06/spurious-wakeup-the-rare-event
ReneS

У меня был похожий опыт, когда я запускал приложение на 40+ основном Unix-сервере. У него было огромное количество ложных пробуждений. - Таким образом, кажется, что количество ложных пробуждений прямо пропорционально количеству процессорных ядер системы.
Bvdb

0

https://stackoverflow.com/a/1461956/14731 содержит отличное объяснение того, почему вам нужно защититься от ложных пробуждений, даже если базовая операционная система их не запускает. Интересно отметить, что это объяснение применимо ко многим языкам программирования, включая Java.


0

Отвечая на вопрос ОП

Что я могу сделать, чтобы внезапно разбудить это ожидание, не ожидая вечно случайного события?

, Нет какого - либо паразитных пробуждения могло проснуться этой ожидающая нить!

Независимо от того, могут ли ложные пробуждения происходить или не происходить на конкретной платформе, в случае с фрагментом OP это определенно невозможно дляCondition.await() вернуться и увидеть строку «Ложные пробуждения!» в выходном потоке.

Если вы не используете очень экзотическую библиотеку классов Java

Это потому , что стандарт, OpenJDK «s ReentrantLockметод» s newCondition()возвращает AbstractQueuedSynchronizer«S реализацию Conditionинтерфейса, вложенная ConditionObject(кстати, это единственная реализация Conditionинтерфейса в этой библиотеке классов), а ConditionObject» метода s await()сам проверяет , имеет ли условие не удерживает, и никакое ложное пробуждение не может заставить этот метод ошибочно вернуться.

Кстати, вы можете проверить это сами, так как довольно легко эмулировать ложное пробуждение, когда AbstractQueuedSynchronizerзадействована основанная реализация. AbstractQueuedSynchronizerиспользует низкоуровневые LockSupport«s parkи unparkметодов, а также, если вы вызываете LockSupport.unparkна поток ожидает на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");
    }
}

и независимо от того, как сильно непаркирующий (основной) поток будет пытаться разбудить ожидающий поток, Condition.await() в этом случае метод никогда не вернется.

Ложные пробуждения о Conditionметодах ожидания обсуждаются в javadoc Conditionинтерфейса . Хотя это говорит о том,

при ожидании условия допускается ложное пробуждение

и это

рекомендуется, чтобы прикладные программисты всегда предполагали, что они могут произойти, и поэтому всегда ожидали в цикле.

но позже добавляет, что

Реализация свободна, чтобы удалить возможность ложных пробуждений

и AbstractQueuedSynchronizerреализация Conditionинтерфейса делает именно это - устраняет любую возможность ложных пробуждений .

Это, безусловно, справедливо и для других ConditionObjectожидающих методов.

Итак, вывод таков:

мы всегда должны вызывать Condition.awaitв цикле и проверять, не выполняется ли условие, но со стандартным OpenJDK библиотека классов Java никогда не может произойти . Если, опять же, вы не используете очень необычную библиотеку классов Java (которая должна быть очень необычной, потому что другие хорошо известные не-OpenJDK библиотеки классов Java, в настоящее время почти вымершие GNU Classpath и Apache Harmony , похоже, идентичны стандартной реализации Conditionинтерфейса)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.