¿En realidad suceden espurias despertares en Java?

208

Al ver varias preguntas relacionadas con el bloqueo y (casi) siempre encontrar el término 'bucle debido a términos espurios de despertar' 1 Me pregunto, ¿alguien ha experimentado este tipo de activación (suponiendo un entorno de hardware / software decente, por ejemplo)?

Sé que el término 'espurio' no significa una razón aparente, pero ¿cuáles pueden ser las razones de este tipo de evento?

( 1 Nota: no estoy cuestionando la práctica de bucle).

Editar: Una pregunta auxiliar (para aquellos a quienes les gustan los ejemplos de código):

Si tengo el siguiente programa y lo ejecuto:

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

¿Qué puedo hacer para despertar esto awaitespuriamente sin esperar por siempre un evento aleatorio?

akarnokd
fuente
1
Para las JVM que se ejecutan en sistemas POSIX y usan pthread_cond_wait()la pregunta real es "¿Por qué pthread_cond_wait tiene despertadores espurios?" .
Flujo

Respuestas:

204

El artículo de Wikipedia sobre despertares espurios tiene este dato :

La pthread_cond_wait()función en Linux se implementa mediante la futexllamada al sistema. Cada llamada al sistema de bloqueo en Linux regresa abruptamente EINTRcuando el proceso recibe una señal. ... pthread_cond_wait()no puede reiniciar la espera porque puede perder un despertar real en el poco tiempo que estuvo fuera de la futexllamada del sistema. Esta condición de carrera solo puede evitarse si la persona que llama comprueba si hay una invariante. Por lo tanto, una señal POSIX generará una activación espuria.

Resumen : si se señala un proceso de Linux, sus subprocesos en espera disfrutarán de un agradable y espurio despertar .

Lo compro. Esa es una píldora más fácil de tragar que la razón generalmente vaga "es por rendimiento" que a menudo se da.

John Kugelman
fuente
13
Mejor explicación aquí: stackoverflow.com/questions/1461913/…
Gili
3
Este desbloqueo EINTR es válido para todas las llamadas al sistema de bloqueo en sistemas derivados de Unix. Esto hizo que el núcleo fuera mucho más simple, pero los programadores de aplicaciones compraron la carga.
Tim Williscroft
2
Pensé que pthread_cond_wait () y sus amigos no podían devolver EINTR, pero devolver cero si se despertaba espuriosamente. De: pubs.opengroup.org/onlinepubs/7908799/xsh/… "Estas funciones no devolverán un código de error de [EINTR]".
gordito
2
@jgubby Eso es correcto. La futex()llamada subyacente regresa EINTR, pero ese valor de retorno no se eleva al siguiente nivel. Por lo tanto, la persona que llama pthread debe verificar si hay una invariante. Lo que dicen es que cuando pthread_cond_wait()regresa, debe verificar su condición de bucle (invariante) nuevamente, porque la espera podría haberse despertado espuriamente. Recibir una señal durante una llamada al sistema es una posible causa, pero no es la única.
John Kugelman
1
Presumiblemente, la pthreadbiblioteca podría proporcionar su propia lógica de verificación invariante y su propia para eliminar los espurios despertares, en lugar de pasar esa responsabilidad al usuario. Eso (presumiblemente) tendría el supuesto impacto en el rendimiento.
22

Tengo un sistema de producción que exhibe este comportamiento. Un hilo espera una señal de que hay un mensaje en la cola. En períodos de mucha actividad, hasta el 20% de los despertares son espurios (es decir, cuando se despierta no hay nada en la cola). Este hilo es el único consumidor de los mensajes. Se ejecuta en una caja de 8 procesadores Linux SLES-10 y está construido con GCC 4.1.2. Los mensajes provienen de una fuente externa y se procesan de forma asíncrona porque hay problemas si mi sistema no los lee lo suficientemente rápido.

Mr.Dirty.Birdy
fuente
15

Para responder la pregunta en el título: ¡Sí!sucede. Aunque el artículo de Wiki menciona mucho sobre los despertares espurios, una buena explicación de lo mismo que encontré es la siguiente:

Solo piense en ello ... como cualquier código, el programador de subprocesos puede experimentar un apagón temporal debido a algo anormal que ocurre en el hardware / software subyacente. Por supuesto, se debe tener cuidado para que esto suceda lo más raro posible, pero dado que no existe un software 100% robusto, es razonable suponer que esto puede suceder y tener cuidado con la recuperación elegante en caso de que el programador detecte esto (p. Ej. observando latidos faltantes).

Ahora, ¿cómo podría recuperarse el planificador, teniendo en cuenta que durante el apagón podría perder algunas señales destinadas a notificar subprocesos en espera? Si el planificador no hace nada, los hilos "desafortunados" mencionados simplemente se colgarán, esperando para siempre; para evitar esto, el planificador simplemente enviaría una señal a todos los hilos en espera.

Esto hace que sea necesario establecer un "contrato" para que el hilo de espera pueda ser notificado sin motivo. Para ser precisos, habría una razón, un apagón del planificador, pero dado que el subproceso está diseñado (por una buena razón) para ser ajeno a los detalles de implementación interna del planificador, es probable que esta razón sea mejor presentarla como "espuria".

Estaba leyendo esta respuesta de Source y la encontré lo suficientemente razonable. También lee

Despiertos espurios en Java y cómo evitarlos .

PD: El enlace de arriba está en mi blog personal que tiene detalles adicionales sobre los despertares espurios.

Aniket Thakur
fuente
9

Cameron Purdy escribió una publicación de blog hace un tiempo sobre el problema espurio de despertar. Entonces sí, sucede

Supongo que está en la especificación (como una posibilidad) debido a las limitaciones de algunas de las plataformas en las que se implementa Java. ¡aunque pueda estar equivocado!

oxbow_lakes
fuente
Leí la publicación y me di una idea acerca de tener pruebas unitarias para probar la conformidad de una aplicación con el paradigma de bucle de espera al despertarla de forma aleatoria / determinista. ¿O ya está disponible en alguna parte?
akarnokd
Es otra pregunta sobre SO: "¿Existe una máquina virtual estricta que pueda usarse para las pruebas?". Me gustaría ver una con estricta memoria local de subprocesos - No creo que todavía existen
oxbow_lakes
8

Solo para agregar esto. Sí, sucede y pasé tres días buscando la causa de un problema de subprocesamiento múltiple en una máquina de 24 núcleos (JDK 6). 4 de 10 ejecuciones experimentaron eso sin ningún patrón. Esto nunca sucedió en 2 núcleos u 8 núcleos.

Estudié material en línea y este no es un problema de Java, sino un comportamiento general raro pero esperado.

ReneS
fuente
Hola ReneS, ¿estás (estabas) desarrollando la aplicación ejecutándose allí? ¿Tiene (did) tiene el método wait () invocando mientras se verifica la condición externa del bucle como se sugiere en java doc docs.oracle.com/javase/6/docs/api/java/lang/… ?
Gumkins
Escribí sobre eso y sí, la solución es un ciclo while con una verificación de condición. Mi error fue el bucle que faltaba ... pero aprendí sobre estos despertadores ... nunca en dos núcleos, a menudo en 24cores blog.xceptance.com/2011/05/06/spurious-wakeup-the-rare-event
ReneS
Tuve experiencias similares cuando ejecuté una aplicación en un servidor unix de más de 40 núcleos. Tenía una cantidad extrema de espurias despertares. - Entonces, parece que la cantidad de activaciones espurias es directamente proporcional a la cantidad de núcleos de procesador del sistema.
bvdb
0

https://stackoverflow.com/a/1461956/14731 contiene una excelente explicación de por qué debe protegerse contra las espurias activaciones, incluso si el sistema operativo subyacente no las activa. Es interesante observar que esta explicación se aplica a múltiples lenguajes de programación, incluido Java.

Gili
fuente
0

Respondiendo la pregunta del OP

¿Qué puedo hacer para despertar esto espurio sin esperar para siempre un evento aleatorio?

, ¡ ningún despertar espurio podría despertar este hilo en espera!

Independientemente de si los despertares espurios pueden o no suceder en una plataforma en particular, en el caso del fragmento del OP, es positivamente imposible paraCondition.await() volver y ver la línea "despertar espurias!" en la secuencia de salida.

A menos que esté utilizando una biblioteca de clases Java muy exótica

Esto es porque estándar, OpenJDK 's ReentrantLockmétodo' s newCondition()devuelve los AbstractQueuedSynchronizer's de la aplicación de Conditioninterfaz, anidado ConditionObject(por cierto, es la única aplicación de Conditioninterfaz de esta biblioteca de clases), y el ConditionObject' método s await()sí mismo comprueba si la condición no hace se mantiene y ningún despertar espurio podría obligar a este método a regresar por error.

Por cierto, puede verificarlo usted mismo, ya que es bastante fácil emular la activación espuria una vez que la AbstractQueuedSynchronizerimplementación basada está involucrada. AbstractQueuedSynchronizerusos de bajo nivel LockSupport's parky unparkmétodos, y si se invoca LockSupport.unparken un hilo en esperaCondition , esta acción no puede distinguirse de un despertar espuria.

Refactorizando ligeramente el fragmento del 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");
    }
}

, y no importa cuánto intente el hilo de estacionamiento (principal) intentar despertar el hilo en espera, el Condition.await() método nunca volverá en este caso.

Los espurios despertares de Conditionlos métodos de espera se discuten en el javadoc de la Conditioninterfaz . Aunque sí dice eso,

cuando se espera una Condición, se permite que ocurra un despertar espurio

y eso

Se recomienda que los programadores de aplicaciones siempre asuman que pueden ocurrir y, por lo tanto, siempre esperen en un bucle.

pero luego agrega que

Una implementación es gratuita para eliminar la posibilidad de despertar espurios

y AbstractQueuedSynchronizerla implementación de la Conditioninterfaz hace exactamente eso: elimina cualquier posibilidad de reactivaciones espurias .

Esto seguramente es válido para ConditionObjectlos métodos de espera de otros.

Entonces, la conclusión es:

siempre debemos llamar Condition.awaital bucle y verificar si la condición no se cumple, pero con OpenJDK estándar, Java Class Library nunca puede suceder . A menos que, de nuevo, utilice la Biblioteca de clases Java muy inusual (que debe ser muy muy inusual, porque otras Bibliotecas de clases Java no OpenJDK conocidas, actualmente GNU Classpath y Apache Harmony , casi extintas , parecen tener una implementación de Conditioninterfaz estándar )

igor.zh
fuente