Quali sono gli usi principali di yield () e in cosa differisce da join () e interrupt ()?


106

Sono un po 'confuso sull'uso del yield()metodo in Java, in particolare nel codice di esempio qui sotto. Ho anche letto che yield () è "usato per impedire l'esecuzione di un thread".

Le mie domande sono:

  1. Credo che il codice riportato di seguito restituisca lo stesso output sia quando lo si utilizza yield()che quando non lo si utilizza. È corretto?

  2. Quali sono, infatti, i principali utilizzi di yield()?

  3. In che modo è yield()diverso dai metodi join()e interrupt()?

L'esempio di codice:

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

Ottengo lo stesso output utilizzando il codice sopra sia con che senza l'utilizzo yield():

Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run

Questa domanda dovrebbe essere chiusa perché troppo ampia .
Raedwald

No. non restituisce lo stesso risultato quando hai yield()e non. quando hai una i grande invece di 5, puoi vedere l'effetto del yield()metodo.
lakshman

Risposte:


97

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

finestre

Nell'implementazione Hotspot, il modo in cui Thread.yield()funziona è cambiato tra Java 5 e Java 6.

In Java 5, Thread.yield()chiama la chiamata API di Windows Sleep(0). Questo ha l'effetto speciale di cancellare il quantum del thread corrente e di metterlo alla fine della coda per il suo livello di priorità . In altre parole, tutti i thread eseguibili con la stessa priorità (e quelli con priorità maggiore) avranno la possibilità di essere eseguiti prima che al thread ceduto venga assegnato il tempo CPU successivo. Quando alla fine verrà riprogrammato, tornerà con un quantum completo completo , ma non "trasferisce" alcuno del quantum rimanente dal momento della resa. Questo comportamento è leggermente diverso da un sonno diverso da zero in cui il thread dormiente perde generalmente 1 valore quantico (in effetti, 1/3 di un tick di 10 o 15 ms).

In Java 6, questo comportamento è stato modificato. La VM Hotspot ora implementa Thread.yield()utilizzando la SwitchToThread()chiamata API di Windows . Questa chiamata fa sì che il thread corrente rinunci al suo timeslice corrente , ma non al suo intero quantum. Ciò significa che a seconda delle priorità di altri thread, il thread cedente può essere pianificato in un periodo di interrupt successivo . (Vedere la sezione sulla pianificazione dei thread per ulteriori informazioni sui timeslices.)

Linux

Sotto Linux, Hotspot chiama semplicemente sched_yield(). Le conseguenze di questa chiamata sono leggermente diverse e forse più gravi rispetto a Windows:

  • un thread ceduto non otterrà un'altra fetta di CPU finché tutti gli altri thread non avranno una fetta di CPU ;
  • (almeno nel kernel 2.6.8 in poi), il fatto che il thread ha prodotto è implicitamente preso in considerazione dall'euristica dello scheduler sulla sua recente allocazione della CPU, quindi, implicitamente, un thread che ha ceduto potrebbe ricevere più CPU quando programmato in il futuro.

(Vedere la sezione sulla pianificazione dei thread per maggiori dettagli sulle priorità e sugli algoritmi di pianificazione.)

Quando usarlo yield()?

Direi praticamente mai . Il suo comportamento non è definito in modo standard e generalmente ci sono modi migliori per eseguire le attività che potresti voler eseguire con yield ():

  • se stai cercando di utilizzare solo una parte della CPU , puoi farlo in un modo più controllabile stimando quanta CPU il thread ha utilizzato nel suo ultimo pezzo di elaborazione, quindi dormendo per un po 'di tempo per compensare: vedi il metodo sleep () ;
  • se stai aspettando che un processo o una risorsa si completi o diventi disponibile, ci sono modi più efficienti per farlo, ad esempio usando join () per attendere il completamento di un altro thread, usando il meccanismo di attesa / notifica per consentire un thread per segnalare a un altro che un'attività è completa o, idealmente, utilizzando uno dei costrutti di concorrenza Java 5 come un semaforo o una coda di blocco .

18
"quantum rimanente", "quantum intero" - da qualche parte lungo la strada qualcuno ha dimenticato cosa significa la parola "quantum"
kbolino

@kbolino Quantum è il nuovo atomo.
Evgeni Sergeev

2
@kbolino - ... latino: "quanto", "quanto" . Non vedo come ciò sia in alcun modo contraddittorio con l'uso sopra. La parola significa semplicemente una quantità descritta di qualcosa, quindi dividerla in parti usate e rimanenti mi sembra perfettamente ragionevole.
Periata Breatta

@PeriataBreatta Immagino abbia più senso se hai familiarità con la parola al di fuori della fisica. La definizione fisica era l'unica che conoscevo.
kbolino

Ho messo una taglia su questa domanda per ottenere questa risposta aggiornata per 7, 8, 9. Modificala con le informazioni attuali su 7,8 e 8 e otterrai la taglia.

40

Vedo che la domanda è stata riattivata con una taglia, chiedendo ora quali sono gli usi pratici yield. Faccio un esempio dalla mia esperienza.

Come sappiamo, yieldforza il thread chiamante a rinunciare al processore su cui è in esecuzione in modo che sia possibile programmare l'esecuzione di un altro thread. Questo è utile quando il thread corrente ha terminato il suo lavoro per ora, ma vuole tornare rapidamente in primo piano e controllare se qualche condizione è cambiata. In che modo è diverso da una variabile di condizione? yieldconsente al thread di tornare molto più rapidamente allo stato di esecuzione. Quando si attende una variabile di condizione, il thread viene sospeso e deve attendere che un thread diverso segnali che dovrebbe continuare.yieldfondamentalmente dice "consenti l'esecuzione di un thread diverso, ma permettimi di tornare a lavorare molto presto poiché mi aspetto che qualcosa cambi nel mio stato molto molto rapidamente". Questo suggerisce una rotazione impegnata, in cui una condizione può cambiare rapidamente ma la sospensione del thread comporterebbe un notevole calo delle prestazioni.

Ma basta balbettare, ecco un esempio concreto: il modello parallelo del fronte d'onda. Un esempio di base di questo problema è il calcolo delle singole "isole" di 1 in un array bidimensionale riempito con 0 e 1. Una "isola" è un gruppo di celle adiacenti l'una all'altra verticalmente o orizzontalmente:

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

Qui abbiamo due isole di 1: in alto a sinistra e in basso a destra.

Una soluzione semplice è eseguire un primo passaggio sull'intero array e sostituire i valori 1 con un contatore incrementale in modo tale che alla fine ogni 1 sia stato sostituito con il suo numero di sequenza nell'ordine principale delle righe:

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

Nella fase successiva, ogni valore viene sostituito dal minimo tra se stesso e i valori dei suoi vicini:

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

Ora possiamo facilmente determinare che abbiamo due isole.

La parte che vogliamo eseguire in parallelo è il passaggio in cui calcoliamo i minimi. Senza entrare troppo nei dettagli, ogni thread riceve le righe in modo intercalato e si basa sui valori calcolati dal thread che elabora la riga sopra. Pertanto, ogni thread deve rimanere leggermente indietro rispetto al thread che elabora la riga precedente, ma deve anche rimanere al passo entro un tempo ragionevole. Maggiori dettagli e un'implementazione sono presentati da me stesso in questo documento . Nota il cui utilizzo sleep(0)è più o meno l'equivalente in C di yield.

In questo caso è yieldstato utilizzato per forzare a turno ogni thread in pausa, ma poiché nel frattempo il thread che elabora la riga adiacente avanzerebbe molto rapidamente, una variabile di condizione si rivelerebbe una scelta disastrosa.

Come puoi vedere, yieldè un'ottimizzazione piuttosto fine. Usarlo nel posto sbagliato, ad esempio aspettando una condizione che cambia raramente, causerà un uso eccessivo della CPU.

Scusa per il lungo balbettio, spero di essere stato chiaro.


1
IIUC quello che presenti nel documento, l'idea è che in questo caso, è più efficiente aspettare occupato, chiamando yieldquando la condizione non è soddisfatta per dare agli altri thread la possibilità di procedere con il calcolo, piuttosto che usare più alte primitive di sincronizzazione dei livelli, giusto?
Petr Pudlák

3
@Petr Pudlák: Sì. Ho confrontato questo rispetto all'utilizzo della segnalazione del thread e la differenza di prestazioni è stata enorme in questo caso. Poiché la condizione può diventare realtà molto rapidamente (questo è il problema chiave), le variabili di condizione sono troppo lente poiché il thread viene sospeso dal sistema operativo, piuttosto che rinunciare alla CPU per un brevissimo periodo di utilizzo yield.
Tudor

@Tudor ottima spiegazione!
Sviluppatore Marius Žilėnas

1
"Nota l'uso di sleep (0) che è più o meno l'equivalente C di yield." .. beh, se vuoi sleep (0) con java, perché non lo usi? Thread.sleep () è una cosa che esiste già. Non sono sicuro che questa risposta fornisca un ragionamento sul motivo per cui si dovrebbe usare Thread.yield () invece di Thread.sleep (0); C'è anche un thread esistente che spiega perché sono diversi.
eis

@eis: Thread.sleep (0) vs Thread.yield () va oltre lo scopo di questa risposta. Citavo Thread.sleep (0) solo per le persone che cercavano un equivalente vicino in C. La domanda riguardava gli usi di Thread.yield ().
Tudor

12

Sulle differenze tra yield(), interrupt()e join()- in generale, non solo in Java:

  1. cedere : letteralmente, "cedere" significa lasciar andare, arrendersi, arrendersi. Un thread cedevole dice al sistema operativo (o alla macchina virtuale, o cosa no) che è disposto a lasciare che altri thread vengano pianificati al suo posto. Questo indica che non sta facendo qualcosa di troppo critico. È solo un suggerimento, però, e non è garantito che abbia alcun effetto.
  2. unione : Quando più thread 'unirsi' su qualche maniglia, o gettone o entità, tutti attendere tutti gli altri thread pertinenti hanno completato l'esecuzione (interamente o fino alla corrispondente propria join). Ciò significa che un sacco di thread hanno completato le loro attività. Quindi ognuno di questi thread può essere programmato per continuare un altro lavoro, potendo presumere che tutte quelle attività siano effettivamente complete. (Da non confondere con SQL Joins!)
  3. interruzione : utilizzato da un thread per "colpire" un altro thread che è inattivo, o in attesa o in fase di collegamento, in modo che sia programmato per continuare a funzionare di nuovo, forse con un'indicazione che è stato interrotto. (Da non confondere con gli interrupt hardware!)

Per Java in particolare, vedere

  1. Partecipare:

    Come utilizzare Thread.join? (qui su StackOverflow)

    Quando partecipare ai thread?

  2. cedendo:

  3. Interruzione:

    Thread.interrupt () è malvagio? (qui su StackOverflow)


Cosa intendi per unire un handle o un token? I metodi wait () e notify () sono su Object, consentendo a un utente di attendere su qualsiasi Object arbitrario. Ma join () sembra meno astratto e deve essere chiamato sullo specifico Thread che vuoi finire prima di continuare ... non è vero?
spaaarky21

@ spaaarky21: intendevo generalmente, non necessariamente in Java. Inoltre, a wait()non è un join, si tratta di un blocco sull'oggetto che il thread chiamante sta tentando di acquisire: attende che il blocco venga liberato da altri e sia stato acquisito dal thread. Ottimizzato di conseguenza la mia risposta.
einpoklum

10

Innanzitutto, la descrizione effettiva è

Fa sì che l'oggetto thread attualmente in esecuzione sospenda temporaneamente e consenta l'esecuzione di altri thread.

Ora, è molto probabile che il tuo thread principale esegua il ciclo cinque volte prima che il runmetodo del nuovo thread venga eseguito, quindi tutte le chiamate a yieldavverranno solo dopo che il ciclo nel thread principale è stato eseguito.

joininterromperà il thread corrente fino al join()termine dell'esecuzione del thread chiamato con .

interruptinterromperà il thread su cui viene chiamato, causando InterrruptException .

yield consente un cambio di contesto ad altri thread, quindi questo thread non consumerà l'intero utilizzo della CPU del processo.


+1. Si noti inoltre che dopo aver chiamato yield (), non c'è ancora alcuna garanzia che lo stesso thread non venga selezionato di nuovo per l'esecuzione, dato un pool di thread con uguale priorità.
Andrew Fielden

Tuttavia, la SwitchToThread()chiamata è migliore di Sleep (0) e questo dovrebbe essere un bug in Java :)
Петър Петров

4

Le risposte attuali non sono aggiornate e richiedono una revisione date le modifiche recenti.

Non c'è alcuna differenza praticaThread.yield() tra le versioni di Java dalla 6 alla 9.

TL; DR;

Conclusioni basate sul codice sorgente di OpenJDK ( http://hg.openjdk.java.net/ ).

Se non si tiene conto del supporto HotSpot delle sonde USDT (le informazioni di tracciamento del sistema sono descritte nella guida dtrace ) e della proprietà JVM, ConvertYieldToSleepil codice sorgente di yield()è quasi lo stesso. Vedere la spiegazione di seguito.

Java 9 :

Thread.yield()chiama il metodo specifico del sistema operativo os::naked_yield():
su Linux:

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

Su Windows:

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

Java 8 e versioni precedenti:

Thread.yield()chiama il metodo specifico del sistema operativo os::yield():
su Linux:

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

Su Windows:

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

Come puoi vedere, Thread.yeald()su Linux è identico per tutte le versioni di Java.
Vediamo Windows os::NakedYield()da 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 differenza tra Java 9 e Java 8 nella verifica aggiuntiva dell'esistenza del SwitchToThread()metodo dell'API Win32 . Lo stesso codice è presente per Java 6.
Il codice sorgente di os::NakedYield()JDK 7 è leggermente diverso ma ha lo stesso comportamento:

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

Il controllo aggiuntivo è stato eliminato a causa del SwitchToThread()metodo disponibile a partire da Windows XP e Windows Server 2003 (vedere le note di msdn ).


2

Quali sono, infatti, i principali utilizzi di yield ()?

Yield suggerisce alla CPU che è possibile interrompere il thread corrente e avviare l'esecuzione dei thread con priorità più alta. In altre parole, assegnare un valore di priorità bassa al thread corrente per lasciare spazio a thread più critici.

Credo che il codice seguente restituisca lo stesso output sia quando si utilizza yield () che quando non lo si utilizza. È corretto?

NO, i due produrranno risultati diversi. Senza un yield (), una volta che il thread ottiene il controllo, eseguirà il ciclo "Inside run" in una volta. Tuttavia, con una yield (), una volta che il thread ottiene il controllo, stamperà una volta "Inside run" e poi passerà il controllo ad un altro thread, se presente. Se nessun thread è in sospeso, questo thread verrà ripreso di nuovo. Quindi ogni volta che "Inside run" viene eseguito, cercherà altri thread da eseguire e se nessun thread è disponibile, il thread corrente continuerà a essere eseguito.

In che modo yield () è diverso dai metodi join () e interrupt ()?

yield () serve per dare spazio ad altri thread importanti, join () serve per aspettare che un altro thread completi la sua esecuzione e interrupt () serve per interrompere un thread attualmente in esecuzione per fare qualcos'altro.


Volevo solo confermare se questa affermazione è vera Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one go? Si prega di precisare.
Abdullah Khan

0

Thread.yield()fa sì che il thread passi dallo stato "In esecuzione" allo stato "Eseguibile". Nota: il thread non passa allo stato "In attesa".


@ PJMeisch, Non esiste uno RUNNINGstato per le java.lang.Threadistanze. Ma ciò non preclude uno stato "in esecuzione" nativo per il thread nativo per il quale Threadun'istanza è un proxy.
Solomon Slow

-1

Thread.yield ()

Quando invochiamo il metodo Thread.yield (), lo scheduler del thread mantiene il thread attualmente in esecuzione nello stato Runnable e seleziona un altro thread con priorità uguale o maggiore. Se non esiste un thread con priorità uguale e superiore, ripianifica il thread yield () chiamante. Ricorda che il metodo yield non fa passare il thread allo stato di attesa o bloccato. Può solo creare un thread da Running State a Runnable State.

aderire()

Quando il join viene richiamato da un'istanza di thread, questo thread dirà al thread attualmente in esecuzione di attendere fino al completamento del thread di unione. Join viene utilizzato nelle situazioni in cui un'attività che dovrebbe essere completata prima del termine dell'attività corrente.


-4

L'utilizzo principale di yield () è per mettere in attesa un'applicazione multi-threading.

tutte queste differenze di metodi sono yield () mette il thread in attesa durante l'esecuzione di un altro thread e torna indietro dopo il completamento di quel thread, join () riunirà l'inizio dei thread eseguendo fino alla fine e di un altro thread da eseguire dopo che il thread ha terminata, interrupt () interromperà l'esecuzione di un thread per un po '.


La ringrazio per la risposta. Tuttavia si limita a ripetere ciò che le altre risposte già descrivono in dettaglio. Sto offrendo la ricompensa per casi d'uso appropriati in cui yielddovrebbe essere usato.
Petr Pudlák
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.