Verim () 'in ana kullanımları nelerdir ve join () ve interrupt ()' dan farkı nedir?


106

yield()Java'da yöntemin kullanımı konusunda , özellikle aşağıdaki örnek kodda biraz kafam karıştı . Ayrıca verim () 'in' bir iş parçacığının yürütülmesini önlemek için kullanıldığını 'okudum.

Sorularım:

  1. Aşağıdaki kodun hem kullanılırken hem de kullanılmadığında aynı çıktıya neden olduğuna inanıyorum yield(). Bu doğru mu?

  2. Aslında, temel kullanım alanları yield()nelerdir?

  3. Ve yöntemlerinden hangi yönlerden yield()farklıdır ?join()interrupt()

Kod örneği:

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

Aynı çıktıyı yukarıdaki kodu kullanarak ve kullanmadan elde ediyorum yield():

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

Bu soru çok geniş olduğu için kapatılmalıdır .
Raedwald

Hayır, sahip olup olmadığınızda aynı sonucu yield()vermez. 5 yerine büyük i değeriniz olduğunda yield()yöntemin etkisini görebilirsiniz .
lakshman

Yanıtlar:


97

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

pencereler

Hotspot uygulamasında Thread.yield()Java 5 ve Java 6 arasında çalışma şekli değişmiştir.

Java 5'te, Thread.yield()Windows API çağrısını çağırır Sleep(0). Bu, mevcut iş parçacığının kuantumunu temizleme ve onu öncelik seviyesi için kuyruğun sonuna koyma gibi özel bir etkiye sahiptir . Başka bir deyişle, aynı önceliğe sahip tüm çalıştırılabilir evreler (ve daha yüksek önceliğe sahip olanlar), verilen iş parçacığına bir sonraki CPU zamanı verilmeden önce çalışma şansı elde edecektir. Sonunda yeniden programlandığında, tam bir kuantum ile geri gelecek , ancak teslim zamanından kalan herhangi bir kuantum "taşımaz". Bu davranış, uyku ipliğinin genellikle 1 kuantum değerini kaybettiği sıfır olmayan bir uykudan biraz farklıdır (aslında, 10 veya 15 ms'lik bir tikin 1 / 3'ü).

Java 6'da bu davranış değiştirildi. Hotspot VM artık Thread.yield()Windows SwitchToThread()API çağrısını kullanarak uyguluyor . Bu çağrı, mevcut iş parçacığının mevcut zaman diliminden vazgeçmesine neden olur , ancak tüm kuantumunu bırakmaz. Bu, diğer iş parçacığının önceliklerine bağlı olarak, akma iş parçacığının daha sonra bir kesme periyodunda geri planlanabileceği anlamına gelir . ( Zaman dilimleri hakkında daha fazla bilgi için iş parçacığı zamanlama bölümüne bakın .)

Linux

Linux altında, Hotspot sadece arar sched_yield(). Bu çağrının sonuçları biraz farklı ve muhtemelen Windows altındakinden daha ciddidir:

  • diğer tüm iş parçacıkları bir CPU dilimine sahip olana kadar verilen bir iş parçacığı başka bir CPU dilimi almayacaktır ;
  • (en azından kernel 2.6.8'den itibaren), iş parçacığının vermiş olduğu gerçeği, planlayıcının son CPU tahsisatındaki buluşsal yöntemi tarafından dolaylı olarak hesaba katılır - bu nedenle, dolaylı olarak, verilen bir iş parçacığına programlandığında daha fazla CPU verilebilir. gelecek.

( Öncelikler ve zamanlama algoritmaları hakkında daha fazla ayrıntı için iş parçacığı planlama bölümüne bakın .)

Ne zaman kullanılmalı yield()?

Neredeyse hiç derdim . Davranışı standart olarak tanımlanmamıştır ve genellikle verim () ile gerçekleştirmek isteyebileceğiniz görevleri gerçekleştirmenin daha iyi yolları vardır:

  • CPU'nun yalnızca bir bölümünü kullanmaya çalışıyorsanız , iş parçacığının son işlem yığınında ne kadar CPU kullandığını tahmin ederek ve ardından telafi etmek için bir süre uyuyarak bunu daha kontrol edilebilir bir şekilde yapabilirsiniz : bkz. uyku () yöntemi;
  • konum eğer bir süreç ya da kaynak için bekleyen kullanılabilir hale komple ya, orada daha verimli Böyle kullanarak olarak bunu yapmanın yolları vardır () katılmak komple başka iş parçacığı için beklemek kullanarak / bekleme bildirmek tek iplik izin mekanizması diğerine bir görevin tamamlandığını veya ideal olarak Semafor veya engelleme kuyruğu gibi Java 5 eşzamanlılık yapılarından birini kullanarak sinyal vermek için .

18
"kalan kuantum", "tüm kuantum" - yol boyunca bir yerlerde birisi "kuantum" kelimesinin ne anlama geldiğini unuttu
kbolino

@kbolino Quantum yeni atomdur.
Evgeni Sergeev

2
@kbolino - ... latin: "kadar", "ne kadar" . Bunun yukarıdaki kullanımla herhangi bir şekilde çelişkili olduğunu görmüyorum. Kelime basitçe bir şeyin tarif edilen miktarı anlamına gelir, bu yüzden onu kullanılmış ve kalan kısımlara bölmek bana tamamen mantıklı geliyor.
Periata Breatta

@PeriataBreatta Sanırım fizik dışındaki kelimeye aşina iseniz daha mantıklı geliyor. Fizik tanımı bildiğim tek şeydi.
kbolino

Bu cevabı 7, 8, 9 için güncellemek için bu soruya bir ödül koydum. 7,8 ve 8 hakkındaki güncel bilgilerle düzenleyin ve ödülü alacaksınız.

40

Sorunun bir ödülle yeniden aktive edildiğini görüyorum, şimdi pratik kullanımlarının ne olduğunu soruyorum yield. Deneyimlerimden bir örnek vereceğim.

Bildiğimiz gibi yield, çağıran iş parçacığını, üzerinde çalıştığı işlemciyi bırakmaya zorlar, böylece başka bir iş parçacığı çalışacak şekilde programlanabilir. Bu, mevcut iş parçacığı şimdilik işini bitirdiğinde ancak kuyruğun başına hızla dönmek ve bazı koşulların değişip değişmediğini kontrol etmek istediğinde kullanışlıdır. Bunun bir koşul değişkeninden farkı nedir? yieldiş parçacığının çalışma durumuna çok daha hızlı dönmesini sağlar. Bir koşul değişkenini beklerken, iş parçacığı askıya alınır ve başka bir iş parçacığının devam etmesi gerektiğini bildirmesi için beklemesi gerekir.yieldtemelde "farklı bir iş parçacığının çalışmasına izin ver, ancak durumumda bir şeyin çok çok hızlı bir şekilde değişmesini beklerken çok kısa sürede işe dönmeme izin ver" diyor. Bu, bir koşulun hızla değişebildiği, ancak ipliğin askıya alınmasının büyük bir performans düşüşüne yol açacağı yoğun eğirmeye yönelik ipuçları verir.

Ama yeterince gevezelik, işte somut bir örnek: dalga cephesi paralel patern. Bu sorunun temel bir örneği, 0'lar ve 1'ler ile dolu iki boyutlu bir dizide 1'lerin tek tek "adalarını" hesaplamaktır. "Ada", dikey veya yatay olarak birbirine bitişik olan bir hücre grubudur:

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

Burada iki adet 1 adamız var: üst sol ve sağ alt.

Basit bir çözüm, tüm dizide bir ilk geçiş yapmak ve 1 değerleri artan bir sayaçla değiştirmektir, böylece sonunda her 1, satır ana sırasına göre sıra numarasıyla değiştirilir:

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

Bir sonraki adımda, her bir değer, kendisi ve komşularının değerleri arasındaki minimum değerle değiştirilir:

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

Artık iki adamız olduğunu kolayca belirleyebiliriz.

Paralel olarak çalışmak istediğimiz kısım, minimumları hesapladığımız adımdır. Çok fazla ayrıntıya girmeden, her iş parçacığı aralıklı olarak satır alır ve yukarıdaki satırı işleyen iş parçacığı tarafından hesaplanan değerlere dayanır. Bu nedenle, her bir iş parçacığı, önceki satırı işleyen iş parçacığının biraz gerisinde kalmalı, ancak aynı zamanda makul bir süre içinde devam etmelidir. Daha fazla ayrıntı ve bir uygulama bu belgede tarafımdan sunulmuştur . Kullanımının sleep(0)aşağı yukarı C eşdeğeri olduğuna dikkat edin yield.

Bu durumda yield, her bir iş parçacığını sırayla durmaya zorlamak için kullanıldı, ancak bitişik sırayı işleyen iş parçacığı bu arada çok hızlı ilerleyeceğinden, bir koşul değişkeni feci bir seçim olacaktır.

Gördüğünüz gibi yieldoldukça ince bir optimizasyon. Yanlış yerde kullanılması, örneğin nadiren değişen bir koşulu beklemek, CPU'nun aşırı kullanımına neden olur.

Uzun gevezelik için özür dilerim, umarım kendimi açıkladım.


1
Belgede sunduğunuz IIUC, fikir şu ki, bu durumda meşgul beklemek, yieldkoşul yerine getirilmediğinde arama yapmak, diğer iş parçacıklarına hesaplamaya devam etme şansı vermek için daha yüksek düzey senkronizasyon ilkelleri, değil mi?
Petr Pudlák

3
@Petr Pudlák: Evet. Bunu iş parçacığı sinyalini kullanarak karşılaştırdım ve bu durumda performans farkı çok büyüktü. Koşul çok hızlı bir şekilde gerçekleşebildiğinden (bu temel konu budur), işlemciden çok kısa bir süre için vazgeçmek yerine, iş parçacığı işletim sistemi tarafından beklemeye alındığından koşul değişkenleri çok yavaştır yield.
Tudor

@Tudor harika bir açıklama!
Geliştirici Marius Žilėnas

1
"Verimin aşağı yukarı C eşdeğeri olan uyku (0) kullanımına dikkat edin." .. peki, java ile uyumak (0) istiyorsanız, neden bunu kullanmayasınız? Thread.sleep () zaten var olan bir şeydir. Bu cevabın neden Thread.sleep (0) yerine Thread.yield () kullanılacağına dair bir mantık sağladığından emin değilim; Ayrıca neden farklı olduklarını açıklayan mevcut bir konu da var .
eis

@eis: Thread.sleep (0) vs Thread.yield () bu cevabın kapsamı dışındadır. C'de yakın bir eşdeğer arayan kişiler için sadece Thread.sleep (0) 'dan bahsediyordum. Soru Thread.yield ()' in kullanımları hakkındaydı.
Tudor

12

Arasındaki farklar hakkında yield(), interrupt()ve join()- genel olarak, sadece Java:

  1. teslim : Kelimenin tam anlamıyla, 'teslim olmak', bırakmak, vazgeçmek, teslim olmak anlamına gelir. Verimli bir iş parçacığı, işletim sistemine (veya sanal makineye veya neyin) onun yerine diğer iş parçacıklarının programlanmasına izin vermeye istekli olduğunu söyler. Bu, çok kritik bir şey yapmadığını gösterir. Yine de bu sadece bir ipucu ve herhangi bir etkisi olacağı garanti edilmiyor.
  2. katılma : Bir tutamaç, belirteç veya varlık üzerinde birden fazla iş parçacığı 'birleştiğinde', tümü diğer tüm iş parçacığı yürütmeyi tamamlayana kadar bekler (tamamen veya kendi ilgili birleşimlerine kadar). Bu, bir grup iş parçacığının hepsinin görevlerini tamamladığı anlamına gelir. Daha sonra bu iş parçacıklarının her biri, tüm bu görevlerin gerçekten tamamlandığını varsayabilmek için diğer işlere devam edecek şekilde planlanabilir. (SQL Joins ile karıştırılmamalıdır!)
  3. interruption : Bir iş parçacığı tarafından uyuyan, bekleyen veya katılan başka bir iş parçacığını 'dürtmek' için kullanılır - böylece, belki de kesintiye uğradığı bir gösterge ile yeniden çalışmaya devam etmesi planlanır. (Donanım kesintileriyle karıştırılmamalıdır!)

Özellikle Java için bkz.

  1. Birleştirme:

    Thread.join nasıl kullanılır? (burada StackOverflow'da)

    Konulara ne zaman katılmalı?

  2. Verim:

  3. Araya girme:

    Thread.interrupt () kötü mü? (burada StackOverflow'da)


Bir tanıtıcıya veya jetona katılmakla ne demek istiyorsun? Wait () ve notify () yöntemleri Object üzerindedir ve kullanıcının herhangi bir keyfi Object üzerinde beklemesine izin verir. Ancak join () daha az soyut görünüyor ve devam etmeden önce bitirmek istediğiniz belirli İş Parçacığında çağrılması gerekiyor ... değil mi?
spaaarky

@ spaaarky21: Java'da değil, genel olarak demek istedim. Ayrıca, a wait()bir birleştirme değildir, çağıran iş parçacığının elde etmeye çalıştığı nesne üzerindeki bir kilitle ilgilidir - kilit başkaları tarafından serbest bırakılana ve iş parçacığı tarafından alınana kadar bekler. Cevabımı buna göre ayarladım.
einpoklum

10

İlk olarak, gerçek açıklama

Şu anda yürütülen iş parçacığı nesnesinin geçici olarak duraklamasına ve diğer iş parçacıklarının yürütülmesine izin vermesine neden olur.

Şimdi, büyük olasılıkla ana iş parçacığınızın run, yeni iş parçacığı yöntemi yürütülmeden önce döngüyü beş kez yürütmesi muhtemeldir , bu nedenle tüm çağrılar yieldyalnızca ana iş parçacığındaki döngü çalıştırıldıktan sonra gerçekleşir.

joinile çağrılan iş parçacığı join()yürütülene kadar geçerli iş parçacığını durdurur .

interruptInterruptedException'a neden olarak çağrıldığı iş parçacığını keser .

yield diğer iş parçacıkları için bağlam geçişine izin verir, bu nedenle bu iş parçacığı sürecin tüm CPU kullanımını tüketmez.


+1. Ayrıca, verim () çağrıldıktan sonra, eşit öncelikli iş parçacıklarından oluşan bir havuz göz önüne alındığında, aynı iş parçacığının tekrar yürütme için seçilmeyeceğinin hala bir garantisi olmadığını unutmayın.
Andrew Fielden

Ancak, SwitchToThread()çağrı Uyku (0) 'dan daha iyidir ve bu Java'da bir hata olmalıdır :)
Петър Петров

4

Mevcut cevaplar güncel değil ve son değişikliklere göre revizyon gerektiriyor.

Java sürümleri arasında 6'dan 9'a kadar pratik bir fark yoktur Thread.yield().

TL; DR;

OpenJDK kaynak koduna dayalı sonuçlar ( http://hg.openjdk.java.net/ ).

USDT problarının HotSpot desteğini (sistem izleme bilgileri dtrace kılavuzunda açıklanmıştır ) ve JVM özelliğini hesaba katmazsanız,ConvertYieldToSleep kaynak kodu yield()hemen hemen aynıdır. Aşağıdaki açıklamaya bakın.

Java 9 :

Thread.yield()işletim sistemine özgü yöntemi çağırır os::naked_yield():
Linux'ta:

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

Windows'ta:

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

Java 8 ve öncesi:

Thread.yield()işletim sistemine özgü yöntemi çağırır os::yield():
Linux'ta:

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

Windows'ta:

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

Gördüğünüz gibi Thread.yeald(), Linux'ta tüm Java sürümleri için aynıdır.
Bakalım Windows os::NakedYield()JDK 8'den:

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

Win32 API SwitchToThread()yönteminin varlığının ek kontrolünde Java 9 ve Java 8 arasındaki fark . Aynı kod Java 6 için de mevcuttur . JDK 7'deki
kaynak kodu os::NakedYield()biraz farklıdır, ancak aynı davranışa sahiptir:

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

SwitchToThread()Yöntem, Windows XP ve Windows Server 2003'ten beri mevcut olduğundan ek denetim kaldırılmıştır ( msdn notlarına bakın ).


2

Aslında, verim () 'in ana kullanımları nelerdir?

Verim, CPU'ya mevcut iş parçacığını durdurabileceğinizi ve daha yüksek önceliğe sahip iş parçacıkları yürütmeye başlayabileceğinizi önerir. Başka bir deyişle, daha kritik iş parçacığı için yer bırakmak üzere mevcut iş parçacığına düşük bir öncelik değeri atamak.

Aşağıdaki kodun hem verim () kullanılırken hem de kullanılmadığında aynı çıktıyla sonuçlandığına inanıyorum. Bu doğru mu?

HAYIR, ikisi farklı sonuçlar verecektir. Bir verim () olmadan, iş parçacığı kontrol altına alındığında, tek seferde 'Inside run' döngüsünü yürütecektir. Bununla birlikte, bir verim () ile, iş parçacığı kontrol altına alındığında, "İç çalıştırma" yı bir kez yazdıracak ve ardından varsa denetimi diğer iş parçacığına devredecektir. Bekleyen ileti dizisi yoksa, bu ileti dizisi tekrar devam edecek. Bu nedenle, "Inside run" her çalıştırıldığında, yürütülecek diğer evreleri arayacaktır ve mevcut evre yoksa, mevcut evre çalışmaya devam edecektir.

Verim (), join () ve interrupt () yöntemlerinden hangi yönlerden farklıdır?

getiri () diğer önemli iş parçacıkları için yer açmak içindir, join () başka bir iş parçacığının yürütülmesini tamamlamasını beklemek içindir ve interrupt (), başka bir şey yapmak için o anda çalışan bir iş parçacığını kesmek içindir.


Bu ifadenin doğru olup olmadığını teyit etmek istediniz Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one gomi? Lütfen açıkla.
Abdullah Khan

0

Thread.yield()iş parçacığının "Çalışıyor" durumundan "Çalıştırılabilir" duruma geçmesine neden olur. Not: İş parçacığının "Bekleniyor" durumuna geçmesine neden olmaz.


@PJMeisch, Örnekler RUNNINGiçin durum yoktur java.lang.Thread. Ancak bu, Threadörneğin bir proxy olduğu yerel iş parçacığı için yerel bir "çalışma" durumunu engellemez .
Solomon Slow

-1

Thread.yield ()

Thread.yield () yöntemini çağırdığımızda, iş parçacığı zamanlayıcı o anda çalışan iş parçacığını Çalıştırılabilir durumda tutar ve eşit önceliğe veya daha yüksek önceliğe sahip başka bir evreyi seçer. Eşit ve daha yüksek öncelikli iş parçacığı yoksa, çağıran verim () iş parçacığını yeniden zamanlar. Verim yönteminin iş parçacığının Bekle veya Engellendi durumuna geçmesini sağlamadığını unutmayın. Yalnızca Çalıştırma Durumundan Çalıştırılabilir Duruma bir iş parçacığı oluşturabilir.

katılmak()

Join bir iş parçacığı örneği tarafından çağrıldığında, bu iş parçacığı şu anda yürütülmekte olan iş parçacığının Katılma iş parçacığı tamamlanana kadar beklemesini söyleyecektir. Join, mevcut görev bitmeden önce tamamlanması gereken bir görevin olduğu durumlarda kullanılır.


-4

verim () ana kullanımı, çok iş parçacıklı bir uygulamayı beklemeye almak içindir.

tüm bu yöntem farklılıkları verim () başka bir iş parçacığı çalıştırırken ve bu iş parçacığı tamamlandıktan sonra geri dönerken iş parçacığını beklemeye alır, join () iş parçacığının sonuna kadar çalıştırarak iş parçacığının başlangıcını ve bu iş parçacığının ardından çalıştırılacak başka bir iş parçacığını bir araya getirir. sona erdiğinde, interrupt () bir iş parçacığının yürütülmesini bir süreliğine durduracaktır.


Cevabınız için teşekkür ederim. Ancak, diğer cevapların zaten ayrıntılı olarak tarif ettiklerini tekrarlıyor. Ödülü, yieldkullanılması gereken uygun kullanım durumları için sunuyorum .
Petr Pudlák
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.