¿Cuáles son los usos principales de yield () y en qué se diferencia de join () e interrupt ()?

106

Estoy un poco confundido sobre el uso del yield()método en Java, específicamente en el código de ejemplo a continuación. También he leído que yield () se 'usa para evitar la ejecución de un hilo'.

Mis preguntas son:

  1. Creo que el siguiente código da como resultado el mismo resultado tanto cuando se usa yield()como cuando no se usa. ¿Es esto correcto?

  2. ¿Cuáles son, de hecho, los principales usos de yield()?

  3. ¿En qué se yield()diferencia de los métodos join()y interrupt()?

El ejemplo de código:

public class MyRunnable implements Runnable {

   public static void main(String[] args) {
      Thread t = new Thread(new MyRunnable());
      t.start();

      for(int i=0; i<5; i++) {
          System.out.println("Inside main");
      }
   }

   public void run() {
      for(int i=0; i<5; i++) {
          System.out.println("Inside run");
          Thread.yield();
      }
   }
}

Obtengo el mismo resultado usando el código anterior con y sin usar yield():

Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run
divz
fuente
Esta pregunta debería cerrarse por ser demasiado amplia .
Raedwald
No. no devuelve el mismo resultado cuando tiene yield()y no. cuando tiene una i grande en lugar de 5, puede ver el efecto del yield()método.
lakshman

Respuestas:

97

Fuente: http://www.javamex.com/tutorials/threads/yield.shtml

Ventanas

En la implementación de Hotspot, la forma en que Thread.yield()funciona ha cambiado entre Java 5 y Java 6.

En Java 5, Thread.yield()llama a la API de Windows Sleep(0). Esto tiene el efecto especial de borrar el cuanto del hilo actual y ponerlo al final de la cola para su nivel de prioridad . En otras palabras, todos los subprocesos ejecutables de la misma prioridad (y aquellos de mayor prioridad) tendrán la oportunidad de ejecutarse antes de que el subproceso generado tenga la siguiente hora de CPU. Cuando finalmente se reprograme, volverá con un cuanto completo completo , pero no "transfiere" nada del cuanto restante desde el momento de ceder. Este comportamiento es un poco diferente de un sueño distinto de cero en el que el hilo de dormir generalmente pierde 1 valor cuántico (en efecto, 1/3 de un tic de 10 o 15 ms).

En Java 6, se cambió este comportamiento. Hotspot VM ahora se implementa Thread.yield()mediante la SwitchToThread()llamada a la API de Windows . Esta llamada hace que el hilo actual renuncie a su intervalo de tiempo actual , pero no a todo su cuanto. Esto significa que, dependiendo de las prioridades de otros subprocesos, el subproceso cedente se puede programar de nuevo en un período de interrupción más tarde . (Consulte la sección sobre programación de subprocesos para obtener más información sobre intervalos de tiempo).

Linux

Bajo Linux, Hotspot simplemente llama sched_yield(). Las consecuencias de esta llamada son un poco diferentes y posiblemente más graves que en Windows:

  • un subproceso cedido no obtendrá otro segmento de CPU hasta que todos los demás subprocesos hayan tenido un segmento de CPU ;
  • (al menos en el kernel 2.6.8 en adelante), el hecho de que el subproceso haya cedido se tiene en cuenta implícitamente por la heurística del planificador en su reciente asignación de CPU; por lo tanto, implícitamente, un subproceso que ha producido podría recibir más CPU cuando se programe en el futuro.

(Consulte la sección sobre programación de subprocesos para obtener más detalles sobre las prioridades y los algoritmos de programación).

¿Cuándo usarlo yield()?

Yo diría que prácticamente nunca . Su comportamiento no está definido de manera estándar y, en general, existen mejores formas de realizar las tareas que quizás desee realizar con yield ():

  • Si está tratando de usar solo una parte de la CPU , puede hacerlo de una manera más controlable estimando cuánta CPU ha usado el hilo en su última porción de procesamiento, luego durmiendo durante un tiempo para compensar: consulte el método sleep () ;
  • Si está esperando que un proceso o recurso se complete o esté disponible, hay formas más eficientes de lograrlo, como usar join () para esperar a que se complete otro hilo, usar el mecanismo de espera / notificación para permitir un hilo para indicarle a otro que una tarea está completa, o idealmente mediante el uso de una de las construcciones de concurrencia de Java 5, como un semáforo o una cola de bloqueo .
Sathwick
fuente
18
"cuántico restante", "cuántico completo" - en algún momento alguien olvidó lo que significa la palabra "cuántico"
kbolino
@kbolino Quantum es el nuevo átomo.
Evgeni Sergeev
2
@kbolino - ... latín: "tanto como", "cuanto" . No veo cómo esto es de ninguna manera contradictorio con el uso anterior. La palabra simplemente significa una cantidad descrita de algo, por lo que dividirlo en partes usadas y restantes me parece perfectamente razonable.
Periata Breatta
@PeriataBreatta Supongo que tiene más sentido si estás familiarizado con la palabra fuera de la física. La definición de física era la única que conocía.
kbolino
Puse una recompensa en esta pregunta para actualizar esta respuesta para 7, 8, 9. Edítela con la información actual sobre 7, 8 y 8 y obtendrá la recompensa.
40

Veo que la pregunta se ha reactivado con una recompensa, ahora preguntando cuáles son los usos prácticos yield. Daré un ejemplo de mi experiencia.

Como sabemos, yieldobliga al subproceso que realiza la llamada a abandonar el procesador en el que se está ejecutando para que se pueda programar la ejecución de otro subproceso. Esto es útil cuando el hilo actual ha terminado su trabajo por ahora pero quiere volver rápidamente al frente de la cola y verificar si alguna condición ha cambiado. ¿En qué se diferencia de una variable de condición? yieldpermite que el hilo vuelva mucho más rápido a un estado de ejecución. Cuando se espera una variable de condición, el subproceso se suspende y debe esperar a que un subproceso diferente indique que debe continuar.yieldbásicamente dice "permitir que se ejecute un hilo diferente, pero permitirme volver al trabajo muy pronto, ya que espero que algo cambie en mi estado muy, muy rápidamente". Esto sugiere un giro ocupado, donde una condición puede cambiar rápidamente pero suspender el hilo incurriría en un gran impacto en el rendimiento.

Pero basta de balbucear, aquí hay un ejemplo concreto: el patrón paralelo de frente de onda. Un ejemplo básico de este problema es calcular las "islas" individuales de 1 en una matriz bidimensional llena de 0 y 1. Una "isla" es un grupo de celdas adyacentes entre sí, ya sea vertical u horizontalmente:

1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 1 1

Aquí tenemos dos islas de 1: arriba a la izquierda y abajo a la derecha.

Una solución simple es hacer una primera pasada sobre toda la matriz y reemplazar los valores de 1 con un contador incremental de modo que al final cada 1 se reemplaza con su número de secuencia en el orden principal de la fila:

1 0 0 0
2 3 0 0
0 0 0 4
0 0 5 6
0 0 7 8

En el siguiente paso, cada valor se reemplaza por el mínimo entre él y los valores de sus vecinos:

1 0 0 0
1 1 0 0
0 0 0 4
0 0 4 4
0 0 4 4

Ahora podemos determinar fácilmente que tenemos dos islas.

La parte que queremos ejecutar en paralelo es el paso donde calculamos los mínimos. Sin entrar en demasiados detalles, cada hilo obtiene filas de manera intercalada y se basa en los valores calculados por el hilo que procesa la fila anterior. Por lo tanto, cada hilo debe retrasarse ligeramente con respecto al hilo que procesa la línea anterior, pero también debe mantenerse al día dentro de un tiempo razonable. En este documento, presento más detalles y una implementación . Tenga en cuenta el uso de sleep(0)que es más o menos el equivalente en C de yield.

En este caso yieldse utilizó para forzar a cada hilo a hacer una pausa, pero dado que el proceso del hilo de la fila adyacente avanzaría muy rápidamente mientras tanto, una variable de condición resultaría una elección desastrosa.

Como puede ver, yieldes una optimización bastante fina. Usarlo en el lugar incorrecto, por ejemplo, esperar en una condición que rara vez cambia, causará un uso excesivo de la CPU.

Perdón por el largo parloteo, espero haberme aclarado.

Tudor
fuente
1
IIUC lo que presenta en el documento, la idea es que en este caso, es más eficiente esperar ocupado, llamando yieldcuando la condición no se cumple para dar a los otros subprocesos la oportunidad de continuar con el cálculo, en lugar de usar más alto primitivas de sincronización de nivel, ¿verdad?
Petr Pudlák
3
@Petr Pudlák: Sí. Evalué esto frente al uso de señalización de subprocesos y la diferencia de rendimiento fue enorme en este caso. Dado que la condición puede volverse verdadera muy rápidamente (este es el problema clave), las variables de condición son demasiado lentas ya que el sistema operativo pone el hilo en espera, en lugar de ceder la CPU por un período de tiempo muy corto usando yield.
Tudor
@Tudor ¡gran explicación!
Desarrollador Marius Žilėnas
1
"Tenga en cuenta el uso de sleep (0) que es más o menos el equivalente en C de yield" ... bueno, si desea dormir (0) con Java, ¿por qué no usarlo? Thread.sleep () es algo que ya existe. No estoy seguro de si esta respuesta proporciona un razonamiento de por qué uno usaría Thread.yield () en lugar de Thread.sleep (0); También hay un hilo existente que explica por qué son diferentes.
EIS
@eis: Thread.sleep (0) vs Thread.yield () está más allá del alcance de esta respuesta. Solo estaba mencionando Thread.sleep (0) para las personas que buscan un equivalente cercano en C. La pregunta era sobre los usos de Thread.yield ().
Tudor
12

Sobre las diferencias entre yield(), interrupt()y join()- en general, no sólo en Java:

  1. ceder : Literalmente, "ceder" significa dejar ir, rendirse, entregarse. Un hilo flexible le dice al sistema operativo (o la máquina virtual, o lo que no) que está dispuesto a permitir que se programen otros hilos en su lugar. Esto indica que no está haciendo algo demasiado crítico. Sin embargo, es solo una pista y no se garantiza que tenga ningún efecto.
  2. unirse : cuando varios subprocesos se 'unen' en algún identificador, token o entidad, todos esperan hasta que todos los demás subprocesos relevantes hayan completado la ejecución (por completo o hasta su propia unión correspondiente). Eso significa que un montón de hilos han completado sus tareas. Luego, cada uno de estos hilos se puede programar para continuar con otro trabajo, pudiendo asumir que todas esas tareas están efectivamente completadas. (¡No confundir con SQL Joins!)
  3. interrupción : Usado por un hilo para 'pinchar' otro hilo que está durmiendo, o esperando, o uniéndose, de modo que esté programado para continuar ejecutándose nuevamente, tal vez con una indicación de que ha sido interrumpido. (¡No confundir con interrupciones de hardware!)

Para Java específicamente, consulte

  1. Unión:

    ¿Cómo usar Thread.join? (aquí en StackOverflow)

    ¿Cuándo unir hilos?

  2. Flexible:

  3. Interrumpiendo:

    ¿Es Thread.interrupt () malvado? (aquí en StackOverflow)

einpoklum
fuente
¿A qué te refieres con unir un identificador o token? Los métodos wait () y notify () están en Object, lo que permite al usuario esperar en cualquier Object arbitrario. Pero join () parece menos abstracto y debe llamarse en el hilo específico que desea finalizar antes de continuar ... ¿no es así?
spaaarky21
@ spaaarky21: quise decir en general, no necesariamente en Java. Además, a wait()no es una combinación, se trata de un bloqueo en el objeto que el subproceso que realiza la llamada está intentando adquirir; espera hasta que otros liberen el bloqueo y el subproceso lo haya adquirido. Modifiqué mi respuesta en consecuencia.
einpoklum
10

Primero, la descripción real es

Hace que el objeto de subproceso que se está ejecutando actualmente se detenga temporalmente y permita que se ejecuten otros subprocesos.

Ahora, es muy probable que su hilo principal ejecute el ciclo cinco veces antes de que se ejecute el runmétodo del nuevo hilo, por lo que todas las llamadas a yieldocurrirán solo después de que se ejecute el ciclo en el hilo principal.

joindetendrá el hilo actual hasta que el hilo con el que se llama join()termine de ejecutarse.

interruptinterrumpirá el hilo al que se llama, provocando InterruptedException .

yield permite un cambio de contexto a otros subprocesos, por lo que este subproceso no consumirá todo el uso de CPU del proceso.

MByD
fuente
+1. También tenga en cuenta que después de llamar a yield (), todavía no hay garantía de que el mismo hilo no sea seleccionado para su ejecución nuevamente, dado un grupo de hilos de igual prioridad.
Andrew Fielden
Sin embargo, SwitchToThread()llamar es mejor que Sleep (0) y esto debería ser un error en Java :)
Петър Петров
4

Las respuestas actuales están desactualizadas y requieren revisión debido a los cambios recientes.

No hay diferencias prácticasThread.yield() entre las versiones de Java desde la 6 hasta la 9.

TL; DR;

Conclusiones basadas en el código fuente de OpenJDK ( http://hg.openjdk.java.net/ ).

Si no se tiene en cuenta la compatibilidad con HotSpot de las sondas USDT (la información de seguimiento del sistema se describe en la guía dtrace ) y la propiedad JVM, el ConvertYieldToSleepcódigo fuente de yield()es casi el mismo. Vea la explicación a continuación.

Java 9 :

Thread.yield()llama al método específico del sistema operativo os::naked_yield():
En Linux:

void os::naked_yield() {
    sched_yield();
}

En Windows:

void os::naked_yield() {
    SwitchToThread();
}

Java 8 y versiones anteriores:

Thread.yield()llama al método específico del sistema operativo os::yield():
En Linux:

void os::yield() {
    sched_yield();
}

En Windows:

void os::yield() {  os::NakedYield(); }

Como puede ver, Thread.yeald()en Linux es idéntico para todas las versiones de Java.
Veamos Windows os::NakedYield()de JDK 8:

os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    if (os::Kernel32Dll::SwitchToThreadAvailable()) {
        return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep(0);
    }
    return os::YIELD_UNKNOWN ;
}

La diferencia entre Java 9 y Java 8 en la verificación adicional de la existencia del SwitchToThread()método de la API Win32 . El mismo código está presente para Java 6. El
código fuente de os::NakedYield()en JDK 7 es ligeramente diferente pero tiene el mismo comportamiento:

    os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    // We use GetProcAddress() as ancient Win9X versions of windows doen't support SwitchToThread.
    // In that case we revert to Sleep(0).
    static volatile STTSignature stt = (STTSignature) 1 ;

    if (stt == ((STTSignature) 1)) {
        stt = (STTSignature) ::GetProcAddress (LoadLibrary ("Kernel32.dll"), "SwitchToThread") ;
        // It's OK if threads race during initialization as the operation above is idempotent.
    }
    if (stt != NULL) {
        return (*stt)() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep (0) ;
    }
    return os::YIELD_UNKNOWN ;
}

La comprobación adicional se ha eliminado debido a que el SwitchToThread()método está disponible desde Windows XP y Windows Server 2003 (consulte las notas de msdn ).

Gregory.K
fuente
2

¿Cuáles son, de hecho, los principales usos de yield ()?

Yield sugiere a la CPU que puede detener el subproceso actual y comenzar a ejecutar subprocesos con mayor prioridad. En otras palabras, asignar un valor de prioridad baja al hilo actual para dejar espacio para hilos más críticos.

Creo que el siguiente código da como resultado el mismo resultado tanto cuando se usa yield () como cuando no se usa. ¿Es esto correcto?

NO, los dos producirán resultados diferentes. Sin un rendimiento (), una vez que el hilo toma el control, ejecutará el ciclo 'Ejecución interior' de una vez. Sin embargo, con un yield (), una vez que el subproceso obtiene el control, imprimirá la 'ejecución interna' una vez y luego entregará el control a otro subproceso, si lo hay. Si no hay ningún hilo pendiente, este hilo se reanudará nuevamente. Por lo tanto, cada vez que se ejecuta "Inside run", buscará otros subprocesos para ejecutar y, si no hay ningún subproceso disponible, el subproceso actual seguirá ejecutándose.

¿En qué se diferencia yield () de los métodos join () e interrupt ()?

yield () es para dar espacio a otros subprocesos importantes, join () es para esperar a que otro subproceso complete su ejecución, e interrupt () es para interrumpir un subproceso actualmente en ejecución para hacer otra cosa.

abbas
fuente
¿Solo quería confirmar si esta afirmación es cierta Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one go? Por favor aclare.
Abdullah Khan
0

Thread.yield()hace que el hilo pase del estado "En ejecución" al estado "Ejecutable". Nota: No hace que el hilo pase al estado "En espera".

Prashant Gunjal
fuente
@PJMeisch, No hay RUNNINGestado para java.lang.Threadinstancias. Pero eso no excluye un estado nativo de "ejecución" para el subproceso nativo para el que una Threadinstancia es un proxy.
Solomon Slow
-1

Thread.yield ()

Cuando invocamos el método Thread.yield (), el programador de subprocesos mantiene el subproceso actualmente en ejecución en el estado Ejecutable y elige otro subproceso de igual prioridad o de mayor prioridad. Si no hay un hilo de prioridad igual y más alta, se reprogramará el hilo de llamada yield (). Recuerde que el método de rendimiento no hace que el hilo pase al estado de espera o bloqueado. Solo puede hacer un hilo desde el estado en ejecución hasta el estado ejecutable.

unirse()

Cuando la unión es invocada por una instancia de subproceso, este subproceso le indicará al subproceso actualmente en ejecución que espere hasta que se complete el subproceso de unión. Join se usa en situaciones en las que una tarea que debe completarse antes de que termine la tarea actual.

Prashant Kumar
fuente
-4

El uso principal de yield () es poner en espera una aplicación de subprocesos múltiples.

todas estas diferencias de métodos son yield () pone el hilo en espera mientras se ejecuta otro hilo y regresa después de la finalización de ese hilo, join () reunirá el comienzo de los hilos ejecutándose hasta el final y de otro hilo para que se ejecute después de que ese hilo haya terminado, interrupt () detendrá la ejecución de un hilo por un tiempo.

K.Hughley
fuente
Gracias por su respuesta. Sin embargo, simplemente repite lo que las otras respuestas ya describen en detalle. Ofrezco la recompensa por los casos de uso adecuados en los que yielddebería usarse.
Petr Pudlák