Jakie są główne zastosowania yield () i czym różni się on od funkcji join () i breaking ()?

106

Jestem trochę zdezorientowany, jeśli chodzi o użycie yield()metody w Javie, szczególnie w przykładowym kodzie poniżej. Czytałem również, że yield () jest „używany do zapobiegania wykonaniu wątku”.

Moje pytania to:

  1. Uważam, że poniższy kod yield()daje takie same wyniki zarówno podczas używania, jak i gdy go nie używasz. Czy to jest poprawne?

  2. Jakie są właściwie główne zastosowania yield()?

  3. Czym yield()różni się od metod join()i interrupt()?

Przykładowy kod:

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

Otrzymuję ten sam wynik, używając powyższego kodu zarówno z użyciem, jak i bez użycia yield():

Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run
divz
źródło
To pytanie powinno zostać zamknięte, ponieważ jest zbyt szerokie .
Raedwald,
Nie, nie zwraca tego samego wyniku, gdy masz yield()i nie. kiedy masz duże i zamiast 5, możesz zobaczyć efekt yield()metody.
lakshman

Odpowiedzi:

97

Źródło: http://www.javamex.com/tutorials/threads/yield.shtml

Windows

W implementacji Hotspot sposób, w jaki Thread.yield()działa, zmienił się między Javą 5 a Java 6.

W języku Java 5 Thread.yield()wywołuje wywołanie interfejsu API systemu Windows Sleep(0). Ma to specjalny efekt w postaci wyczyszczenia kwantu bieżącego wątku i umieszczenia go na końcu kolejki ze względu na jego poziom priorytetu . Innymi słowy, wszystkie uruchamialne wątki o tym samym priorytecie (i te o wyższym priorytecie) będą miały szansę na uruchomienie, zanim uzyskany wątek otrzyma następny czas procesora. Kiedy ostatecznie zostanie ponownie zaplanowany, wróci z pełnym kwantem , ale nie „przenosi” żadnego z pozostałych kwantów z czasu uzyskania. To zachowanie różni się nieco od niezerowego snu, w którym śpiąca nić na ogół traci 1 wartość kwantową (w efekcie 1/3 taktu 10 lub 15 ms).

W Javie 6 to zachowanie zostało zmienione. Maszyna wirtualna Hotspot jest teraz implementowana Thread.yield()przy użyciu SwitchToThread()wywołania interfejsu API systemu Windows . To wywołanie powoduje, że bieżący wątek rezygnuje z obecnego krotności czasu , ale nie w całości. Oznacza to, że w zależności od priorytetów innych wątków, wątek generujący może zostać zaplanowany z powrotem w jednym okresie przerwania później . (Zobacz sekcję dotyczącą planowania wątków, aby uzyskać więcej informacji na temat okresów).

Linux

Pod Linuksem Hotspot po prostu dzwoni sched_yield(). Konsekwencje tego wywołania są nieco inne i prawdopodobnie bardziej dotkliwe niż w systemie Windows:

  • uzyskany wątek nie otrzyma kolejnego wycinka procesora, dopóki wszystkie inne wątki nie będą miały jego części ;
  • (przynajmniej w jądrze 2.6.8 i nowszych), fakt, że wątek przyniósł wynik, jest niejawnie brany pod uwagę przez heurystykę harmonogramu w odniesieniu do jego ostatniej alokacji procesora - w ten sposób, domyślnie, wątek, który wygenerował, może otrzymać więcej procesora, gdy zostanie zaplanowany w przyszłość.

(Zobacz sekcję dotyczącą planowania wątków, aby uzyskać więcej informacji na temat priorytetów i algorytmów planowania).

Kiedy używać yield()?

Powiedziałbym, że praktycznie nigdy . Jego zachowanie nie jest standardowo zdefiniowane i ogólnie istnieją lepsze sposoby wykonywania zadań, które możesz chcieć wykonać za pomocą yield ():

  • jeśli próbujesz użyć tylko części procesora , możesz to zrobić w bardziej kontrolowany sposób, szacując, ile procesora wykorzystał wątek w ostatniej porcji przetwarzania, a następnie spać przez pewien czas, aby to zrekompensować: zobacz (uśpienia) sposobu według wynalazku;
  • jeśli czekasz na ukończenie lub udostępnienie procesu lub zasobu , istnieją bardziej wydajne sposoby na osiągnięcie tego, na przykład użycie funkcji join () do czekania na zakończenie innego wątku, użycie mechanizmu czekania / powiadamiania , aby zezwolić na jeden wątek aby zasygnalizować innemu, że zadanie zostało zakończone, lub najlepiej przy użyciu jednej z konstrukcji współbieżności Java 5, takiej jak semafor lub kolejka blokująca .
Sathwick
źródło
18
„pozostały kwant”, „cały kwant” - gdzieś po drodze ktoś zapomniał, co oznacza słowo „kwant”
kbolino
@kbolino Quantum to nowy atom.
Evgeni Sergeev
2
@kbolino - ... latin: "tyle co", "ile" . Nie widzę, żeby było to w jakikolwiek sposób sprzeczne z powyższym użyciem. To słowo oznacza po prostu opisaną ilość czegoś, więc podzielenie go na części używane i pozostałe wydaje mi się całkowicie rozsądne.
Periata Breatta
@PeriataBreatta Wydaje mi się, że ma to większy sens, jeśli znasz słowo poza fizyką. Definicja fizyki była jedyną, jaką znałem.
kbolino
Wyznaczam nagrodę za to pytanie, aby zaktualizować tę odpowiedź na 7, 8, 9. Edytuj ją, podając aktualne informacje o 7,8 i 8, a otrzymasz nagrodę.
40

Widzę, że pytanie zostało reaktywowane z nagrodą, teraz pytam, jakie są praktyczne zastosowania yield. Podam przykład z mojego doświadczenia.

Jak wiemy, yieldwymusza na wątku wywołującym rezygnację z procesora, na którym działa, aby można było zaplanować uruchomienie innego wątku. Jest to przydatne, gdy bieżący wątek zakończył na razie swoją pracę, ale chce szybko wrócić na początek kolejki i sprawdzić, czy jakiś warunek się zmienił. Czym to się różni od zmiennej warunkowej? yieldumożliwia wątkowi powrót do stanu działania o wiele szybciej. Podczas oczekiwania na zmienną warunku wątek jest zawieszany i musi czekać, aż inny wątek zasygnalizuje, że powinien kontynuować.yieldpo prostu mówi: „pozwól działać innemu wątkowi, ale pozwól mi wrócić do pracy bardzo szybko, gdy spodziewam się, że coś bardzo szybko się zmieni w moim stanie”. Wskazuje to na zajęty wirowanie, w którym stan może się szybko zmienić, ale zawieszenie wątku spowodowałoby duży spadek wydajności.

Ale dość bełkotania, oto konkretny przykład: równoległy wzór czoła fali. Podstawowym przykładem tego problemu jest obliczanie poszczególnych „wysp” jedynek w dwuwymiarowej tablicy wypełnionej zerami i jedynkami. „Wyspa” to grupa komórek sąsiadujących ze sobą w pionie lub w poziomie:

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

Tutaj mamy dwie wyspy 1: lewy górny i prawy dolny.

Prostym rozwiązaniem jest wykonanie pierwszego przejścia przez całą tablicę i zastąpienie 1 wartości licznikiem zwiększającym się tak, że na końcu każda 1 została zastąpiona numerem kolejnym w wierszu głównym:

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

W następnym kroku każda wartość jest zastępowana przez minimum między sobą a wartościami sąsiadów:

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

Możemy teraz łatwo określić, że mamy dwie wyspy.

Część, którą chcemy uruchomić równolegle, to krok, w którym obliczamy minimum. Bez wchodzenia w zbyt wiele szczegółów każdy wątek otrzymuje wiersze w sposób przeplatany i opiera się na wartościach obliczonych przez wątek przetwarzający powyższy wiersz. W związku z tym każdy wątek musi być nieco opóźniony w stosunku do wątku przetwarzającego poprzednią linię, ale musi również nadążyć w rozsądnym czasie. Więcej szczegółów i implementację przedstawiam w tym dokumencie . Należy zauważyć, wykorzystanie sleep(0)co jest mniej więcej równoważne C yield.

W tym przypadku yieldzostała użyta w celu wymuszenia na każdym wątku z kolei wstrzymania, ale ponieważ w międzyczasie wątek przetwarzający sąsiedni rząd przesuwałby się bardzo szybko, zmienna warunkowa okazałaby się katastrofalnym wyborem.

Jak widać, yieldjest to dość drobnoziarnista optymalizacja. Używanie go w niewłaściwym miejscu, np. Oczekiwanie na rzadko zmieniający się warunek, spowoduje nadmierne wykorzystanie procesora.

Przepraszam za długą paplaninę, mam nadzieję, że wyraziłem się jasno.

Tudor
źródło
1
IIUC to, co przedstawiasz w dokumencie, chodzi o to, że w tym przypadku bardziej wydajne jest oczekiwanie zajęte, wywołanie, yieldgdy warunek nie jest spełniony, aby dać innym wątkom szansę na kontynuowanie obliczeń, zamiast używania większej prymitywy synchronizacji poziomu, prawda?
Petr Pudlák,
3
@Petr Pudlák: Tak. Porównałem to z wykorzystaniem sygnalizacji wątków i różnica w wydajności była w tym przypadku ogromna. Ponieważ warunek może stać się prawdziwy bardzo szybko (jest to kluczowy problem), zmienne warunku są zbyt wolne, ponieważ wątek jest wstrzymywany przez system operacyjny, zamiast rezygnować z procesora na bardzo krótki czas yield.
Tudor
@Tudor świetne wyjaśnienie!
Deweloper Marius Žilėnas
1
„Zwróć uwagę na użycie funkcji sleep (0), która jest mniej więcej odpowiednikiem wydajności w języku C.”… Cóż, jeśli chcesz, aby funkcja sleep (0) była używana w javie, dlaczego nie miałbyś tego po prostu użyć? Thread.sleep () to rzecz, która już istnieje. Nie jestem pewien, czy ta odpowiedź dostarcza powodów, dla których należałoby użyć Thread.yield () zamiast Thread.sleep (0); Istnieje również wątek wyjaśniający, dlaczego są różne.
eis
@eis: Thread.sleep (0) vs Thread.yield () wykracza poza zakres tej odpowiedzi. Wspominałem o Thread.sleep (0) tylko dla osób szukających bliskiego odpowiednika w C. Pytanie dotyczyło zastosowań Thread.yield ().
Tudor
12

O różnicach między yield(), interrupt()i join()- w ogóle, nie tylko w Javie:

  1. ustąpienie : Dosłownie „ustąpić” oznacza puścić, poddać się, poddać. Wątek przynoszący wyniki informuje system operacyjny (lub maszynę wirtualną lub co nie), że jest skłonny zezwolić na planowanie innych wątków. Oznacza to, że nie robi czegoś zbyt krytycznego. Jest to jednak tylko wskazówka i nie ma gwarancji, że przyniesie to jakikolwiek efekt.
  2. dołączanie : Kiedy wiele wątków „dołącza się” na jakimś dojściu, tokenie lub encji, wszystkie z nich czekają, aż wszystkie inne odpowiednie wątki zakończą wykonywanie (całkowicie lub aż do ich własnego odpowiedniego sprzężenia). Oznacza to, że wszystkie wątki zakończyły swoje zadania. Następnie każdy z tych wątków może zostać zaplanowany do kontynuowania innej pracy, będąc w stanie założyć, że wszystkie te zadania są rzeczywiście ukończone. (Nie mylić z połączeniami SQL!)
  3. przerwa : Używany przez jeden wątek do `` wtykania '' innego wątku, który śpi, czeka lub dołącza - tak, aby zaplanowano kontynuację działania, być może ze wskazaniem, że został przerwany. (Nie mylić z przerwaniami sprzętowymi!)

W przypadku języka Java zobacz

  1. Łączący:

    Jak korzystać z Thread.join? (tutaj w StackOverflow)

    Kiedy dołączyć do wątków?

  2. Wydajność:

  3. Przerywanie:

    Czy Thread.interrupt () jest zły? (tutaj w StackOverflow)

einpoklum
źródło
Co masz na myśli, mówiąc o dołączeniu do uchwytu lub tokena? Metody wait () i notify () działają na Object, pozwalając użytkownikowi czekać na dowolny obiekt. Jednak funkcja join () wydaje się mniej abstrakcyjna i przed kontynuowaniem należy ją wywołać w określonym wątku, który chcesz zakończyć ... prawda?
spaaarky21
@ spaaarky21: Miałem na myśli ogólnie, niekoniecznie w Javie. Ponadto a wait()nie jest złączeniem, chodzi o blokadę obiektu, który próbuje uzyskać wywołujący wątek - czeka, aż blokada zostanie zwolniona przez innych i zostanie przejęta przez wątek. Odpowiednio poprawiłem moją odpowiedź.
einpoklum
10

Po pierwsze, rzeczywisty opis to

Powoduje, że aktualnie wykonywany obiekt wątku tymczasowo wstrzymuje i zezwala na wykonywanie innych wątków.

Teraz jest bardzo prawdopodobne, że twój główny wątek wykona pętlę pięć razy, zanim runmetoda nowego wątku zostanie wykonana, więc wszystkie wywołania yieldbędą miały miejsce dopiero po wykonaniu pętli w głównym wątku.

joinzatrzyma bieżący wątek do momentu zakończenia join()wykonywania wywoływanego wątku .

interruptprzerwie wątek, w którym jest wywoływany, powodując InterruptedException .

yield umożliwia przełączanie kontekstu na inne wątki, więc ten wątek nie będzie zużywał całego wykorzystania procesora przez proces.

MByD
źródło
+1. Należy również pamiętać, że po wywołaniu yield () nadal nie ma gwarancji, że ten sam wątek nie zostanie ponownie wybrany do wykonania, biorąc pod uwagę pulę wątków o równym priorytecie.
Andrew Fielden
Jednak SwitchToThread()połączenie jest lepsze niż uśpienie (0) i powinno to być błąd w Javie :)
Петър Петров
4

Obecne odpowiedzi są nieaktualne i wymagają korekty, biorąc pod uwagę ostatnie zmiany.

Nie ma praktycznej różnicy Thread.yield()między wersjami Java od 6 do 9.

TL; DR;

Wnioski oparte na kodzie źródłowym OpenJDK ( http://hg.openjdk.java.net/ ).

Jeśli nie wziąć pod uwagę obsługi HotSpot sond USDT (informacje o śledzeniu systemu są opisane w przewodniku dtrace ) i właściwości JVM, ConvertYieldToSleepto kod źródłowy yield()jest prawie taki sam. Zobacz wyjaśnienie poniżej.

Java 9 :

Thread.yield()wywołuje metodę specyficzną dla systemu operacyjnego os::naked_yield():
W systemie Linux:

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

W systemie Windows:

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

Java 8 i starsze:

Thread.yield()wywołuje metodę specyficzną dla systemu operacyjnego os::yield():
W systemie Linux:

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

W systemie Windows:

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

Jak widać, Thread.yeald()w systemie Linux jest identyczny dla wszystkich wersji Java.
Zobaczmy Windows os::NakedYield()z 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 ;
}

Różnica między Java 9 i Java 8 SwitchToThread()polega na dodatkowym sprawdzeniu istnienia metody Win32 API . Ten sam kod jest obecny w Javie 6.
Kod źródłowy os::NakedYield()w JDK 7 jest nieco inny, ale zachowuje się tak samo:

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

Dodatkowa kontrola została pominięta ze względu na SwitchToThread()metodę, która jest dostępna od wersji Windows XP i Windows Server 2003 (patrz uwagi msdn ).

Gregory.K
źródło
2

Jakie są tak naprawdę główne zastosowania yield ()?

Wydajność sugeruje procesorowi, że możesz zatrzymać bieżący wątek i rozpocząć wykonywanie wątków z wyższym priorytetem. Innymi słowy, przypisanie wartości o niskim priorytecie do bieżącego wątku, aby zostawić miejsce na bardziej krytyczne wątki.

Uważam, że poniższy kod daje takie same wyniki zarówno przy użyciu yield (), jak i gdy go nie używamy. Czy to jest poprawne?

NIE, oba przyniosą różne rezultaty. Bez yield (), gdy wątek uzyska kontrolę, wykona pętlę „Inside run” za jednym razem. Jednak w przypadku yield (), gdy wątek uzyska kontrolę, raz wydrukuje 'bieg wewnętrzny', a następnie przekaże kontrolę innemu wątkowi, jeśli taki istnieje. Jeśli nie ma wątku oczekującego, zostanie wznowiony ponownie. Tak więc za każdym razem, gdy wykonywane jest "Uruchomienie wewnętrzne", będzie szukał innych wątków do wykonania, a jeśli żaden wątek nie jest dostępny, bieżący wątek będzie wykonywany dalej.

W jaki sposób yield () różni się od metod join () i breaking ()?

yield () służy do zapewniania miejsca innym ważnym wątkom, join () służy do czekania na zakończenie wykonywania przez inny wątek, a przerywanie () służy do przerywania aktualnie wykonywanego wątku w celu zrobienia czegoś innego.

abbas
źródło
Chciałeś tylko potwierdzić, czy to stwierdzenie jest prawdziwe Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one go? Proszę o wyjaśnienie.
Abdullah Khan
0

Thread.yield()powoduje przejście wątku ze stanu „Running” do stanu „Runnable”. Uwaga: nie powoduje przejścia wątku do stanu „Oczekiwanie”.

Prashant Gunjal
źródło
@PJMeisch, Nie ma RUNNINGstanu dla java.lang.Threadinstancji. Nie wyklucza to jednak natywnego stanu „uruchomionego” wątku natywnego, dla którego Threadinstancja jest proxy.
Solomon Slow
-1

Thread.yield ()

Kiedy wywołujemy metodę Thread.yield (), harmonogram wątków utrzymuje aktualnie działający wątek w stanie Runnable i wybiera inny wątek o równym lub wyższym priorytecie. Jeśli nie ma wątku o równym i wyższym priorytecie, zmienia harmonogram wywoływania wątku yield (). Pamiętaj, że metoda zysku nie powoduje przejścia wątku do stanu Oczekiwanie lub Zablokowanie. Może tylko zmienić stan wątku z Running State na Runnable State.

Przystąp()

Gdy łączenie jest wywoływane przez instancję wątku, ten wątek poinformuje aktualnie wykonywany wątek, aby czekał na zakończenie wątku dołączania. Łączenie jest używane w sytuacjach, gdy zadanie, które powinno zostać zakończone przed zakończeniem bieżącego zadania.

Prashant Kumar
źródło
-4

Głównym zastosowaniem yield () jest zawieszanie aplikacji obsługującej wiele wątków.

wszystkie te różnice w metodach to yield () wstrzymuje wątek podczas wykonywania innego wątku i powraca po zakończeniu tego wątku, join () połączy razem początek wątków, wykonując aż do końca i kolejnego wątku do uruchomienia po tym wątku zakończone, przerwanie () zatrzyma na chwilę wykonywanie wątku.

K.Hughley
źródło
Dziękuję za Twoją odpowiedź. Jednak po prostu powtarza to, co szczegółowo opisują inne odpowiedzi. Oferuję nagrodę za właściwe przypadki użycia, w których yieldnależy je wykorzystać.
Petr Pudlák