Birkaç channel'ı beklemek
select bir switch'e benzer, ama her case bir channel gönderimi ya da alımıdır. Bir case ilerleyebilene kadar bloklanır ve sonra onu çalıştırır.
Bu gecikmelerle ilk select hızlı sonucu, ikincisi yavaş olanı alır. İki alımın da diğer channel'ı beklemesi gerekmez. Düz bir <-slow ardından <-fast, hangisi önce gelirse gelsin onları sabit bir sırayla ele alırdı.
select nasıl değerlendirir:
- Tüm channel ifadeleri ve gönderilecek değerler,
selectbaşladığında kaynak sırasıyla bir kez değerlendirilir. - Bir ya da daha fazla case hazırsa biri rastgele seçilir.
- Hiçbiri hazır değilse ve bir
defaultvarsadefaultçalışır. - Aksi halde goroutine bir case hazır olana kadar bloklanır.
Boş bir select {} sonsuza kadar bloklanır. Asıl işi başka goroutine'lerde yapılan programlarda onu ara sıra main'in sonunda görürsünüz.
Hazır case'ler arasında rastgele seçim
Birkaç case aynı anda hazır olduğunda select ilk listeleneni tercih etmez. Bu program iki buffer'lı channel'ı doldurur ve sonra 1000 kez select yapar:
countA ile countB arasındaki dağılım her çalıştırmada değişir ve her biri 500 civarına düşer. Rastgele seçim bilinçlidir: meşgul bir channel'ın diğerlerini aç bırakmasını önler. Önceliğe ihtiyacınız varsa aşağıdaki kalıba bakın.
default ile bloklamayan işlemler
Bir default case'iyle select asla bloklanmaz. Bu, bir gönderim ya da alımı bir "dene" işlemine dönüştürür:
Bir buffer dolduğunda işi atmak, çağıranı hiç durdurmadan yükü azaltmanın ya da metrik yaymanın yoludur.
Channel'ları tekrar tekrar "kontrol etmek" için bir for döngüsü içindeki select'e default koymayın. Hiçbir şey hazır değilken döngü %100 CPU'da boşa döner. Bunun yerine bloklanın ve periyodik olarak uyanmanız gerekiyorsa bir timeout case'i ekleyin.
Timeout'lar
time.After(d), d'den sonra bir kez değer alan bir channel döndürür. Onu asıl işle yarıştırın:
İlk çağrı "data" döndürür. İkincisi, worker bitirmeden çok önce, 50 ms sonra timeout hatasını döndürür. Sonuç channel'ının 1'lik buffer'ı bilinçlidir. Timeout kazandığında kimse result'tan hiç almaz; buffer'sız bir channel ile worker goroutine'i gönderiminde sonsuza kadar bloklanır ve sızardı.
Bir döngüde time.After her yinelemede yeni bir timer oluşturur; bu, mesaj başına bir boşta kalma timeout'u ("1 saniye boyunca mesaj yok") için tam olarak doğrudur. Birçok işlem boyunca genel bir deadline için döngüden önce tek bir timer ya da context oluşturun. Go 1.23'ten beri artık başvurulmayan timer'lar tetiklenmemiş olsalar bile çöp toplayıcı tarafından toplanır, bu yüzden döngüdeki time.After eski sürümlerde olduğu gibi her timer tetiklenene kadar bellek tutmaz (bu, go.mod içinde go 1.23 ya da sonrasını gerektirir).
for-select döngüleri ve quit channel'ları
Durması söylenene kadar çalışan bir goroutine, biri iş, biri durmak için iki case'i olan bir select'i saran bir for döngüsüdür:
quit'e göndermek yerine onu kapatmak deyimsel yoldur: bir close şimdi ve sonra her alıcı tarafından görülür, bu yüzden tek bir close istediğiniz sayıda worker'ı durdurur. done channel'ı main'in worker gerçekten dönene kadar beklemesini sağlar.
Gerçek kodda quit channel'ı genellikle bir context.Context'tir: case <-ctx.Done():. Aynı şekilde çalışır (Done() iptalde kapatılan bir channel döndürür) ve ayrıca deadline'ları ve durma nedenini taşır. Context sayfası onu anlatıyor.
select içinde break
Bir select case'indeki break, çevreleyen for'dan değil select'ten çıkar. Bu, hiç bitmeyen döngülerin yaygın bir kaynağıdır. return kullanın ya da döngüyü etiketleyin:
Channel'lar arasında öncelik
select rastgele seçtiği için tek bir ifade içinde case'leri sıralayamazsınız. Bir channel'ın bir şeyi olduğunda her zaman kazanmasını sağlamak için önce onu tek başına kontrol edin:
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
select {
case <-ctx.Done():
return ctx.Err()
case job := <-jobs:
handle(job)
}
}
İptal zaten olduysa ilk select hemen döner. O olmadan, sürekli gelen bir iş akışı ctx iptal edildikten sonra bir süre rastgele seçimi kazanmaya devam edebilirdi.
Nil channel'lar bir case'i devre dışı bırakır
Bir nil channel'a gönderim ya da ondan alım asla hazır olmaz, bu yüzden bir nil channel üzerindeki case fiilen kapatılmış olur. Kapatılmış bir girdiyi nil yapmak, diğerleriyle devam ederken onun üzerinde select yapmayı bırakmanın yoludur; channels sayfası bu şekilde kurulmuş bir birleştirme döngüsü gösteriyor. Aynı hile bir timeout'u açıp kapatır: var timeout <-chan time.Time'ı ihtiyacınız olana kadar nil tutun, sonra time.After(d) atayın.
Sık yapılan hatalar
- Kaynak sırası beklemek. İlk listelenen case tercih edilmez.
defaultile meşgul döngü. Yapacak hiçbir şeyi olmayan birfor { select { ... default: } }bir CPU çekirdeğini yakar.- Kaybedeni sızdırmak. Bir timeout kazandığında sonucu gönderecek olan goroutine'in yine bitirebilmesi gerekir. Channel'ına 1'lik bir buffer verin.
- Yalnızca
select'ten çıkanbreak. Bir etiket ya dareturnkullanın. - Genel bir deadline olarak döngü yinelemesi başına bir
time.After. Her yinelemede yeniden başlar; deadline'ı döngünün dışında bir kez oluşturun.
Sıkça Sorulan Sorular
Go'da select ne yapar?
select, channel işlemlerinden biri (bir gönderim ya da alım) ilerleyebilene kadar bekler, sonra o case'i çalıştırır. Birden fazlası aynı anda hazırsa rastgele birini seçer. default case'i olmadan bir case hazır olana kadar bloklanır; default ile asla bloklanmaz.
Go'da bir channel alımına timeout nasıl eklenir?
Alımı ve bir timer'ı tek bir select içine koyun: select { case v := <-ch: use(v); case <-time.After(2 * time.Second): return errTimeout }. Hangisi önce olursa o kazanır. Bir döngünün içinde ya da çağıranın zaten bir deadline'ı olduğunda bunun yerine context.WithTimeout ile bir context.Context kullanın ve ctx.Done() üzerinde select yapın.
Go'da select case'leri sırayla mı seçer?
Hayır. Birden fazla case hazır olduğunda Go tekdüze rastgele seçer, böylece hiçbir case diğerlerini aç bırakamaz. Önceliğe ihtiyacınız varsa önce yüksek öncelikli channel'ı default'lu kendi select'inde kontrol edin, sonra tüm channel'lar üzerinde bir select'e geçin.
break neden for-select döngümden çıkmıyor?
Bir select içinde break, çevreleyen for'dan değil yalnızca select ifadesinden çıkar. return kullanın ya da döngüye bir etiket koyun (loop: for { select { case <-done: break loop } }).