Чи справді трапляються помилкові пробудження на 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, але повернути нуль, якщо неправдиво прокинуться? Від: pubs.opengroup.org/onlinepubs/7908799/xsh/… "Ці функції не повертають код помилки [EINTR]."
gubby

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: "Чи є суворий VM, який можна використовувати для тестування?". Я хотів би побачити його із суворою локальною пам'яттю - я не думаю, що вони ще існують
oxbow_lakes

8

Просто, щоб додати це. Так, це трапляється, і я провів три дні на пошуках причини багатопотокової проблеми на 24-ядерній машині (JDK 6). 4 з 10 страт зазнали цього без будь-якого зразка. Це ніколи не траплялося на 2 ядрах або 8 ядрах.

Вивчили деякі матеріали в Інтернеті, і це не проблема Java, а загальна рідкісна, але очікувана поведінка.


Привіт, РенеС, чи розробляли ви додаток, що працює там? Чи має (у) спосіб увімкнення методу wait () під час перевірки зовнішнього стану циклу, як це пропонується в java doc docs.oracle.com/javase/6/docs/api/java/lang/… ?
гумки

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

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

0

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


0

Відповідаючи на питання ОП

Що я можу зробити, щоб розбудити це очікування неправдиво, не чекаючи вічно випадкової події?

, жоден хибний пробудження не міг розбудити цю очікувану нитку!

Незалежно від того , паразитні пробуджень може або не може статися на конкретній платформі, в разі ФП - х фрагмент коду це позитивно неможливо для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 цю дію не може відрізнити від помилкового пробудження.

Трохи переробляючи фрагмент ОП,

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методів очікування очікуються в інтерфейсі javadocCondition . Хоча це і говорить,

під час очікування стану, допускається хибне пробудження

і це

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

але згодом це додає

Реалізація безкоштовна, щоб усунути можливість помилкових пробуджень

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

Це, безумовно, справедливо для інших ConditionObjectочікуючих методів.

Отже, висновок такий:

ми завжди повинні зателефонувати Condition.awaitв цикл і перевірити, чи не виконується умова, але зі стандартними OpenJDK, бібліотекою класів Java ніколи не може відбутися . Якщо, знову ж, ви не використовуєте дуже незвичайну бібліотеку класів Java (що має бути дуже незвично, тому що інші відомі бібліотеки класів Java, які не є OpenJDK, на даний момент майже вимерлі Class Class і GNU Classpath та Apache Harmony , здається, є ідентичними стандартній реалізації Conditionінтерфейсу)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.