Ders 04 / 14
select Deyimi
Birden çok kanal üzerinde aynı anda beklemenin karşılığı select deyimidir: yalnız bir dal hazırsa o dal belirlenimci biçimde seçilir, hiçbiri hazır değilse ve bir default dalı varsa o dal seçilir. Yazım sırası bir öncelik taşımaz. Sınırlayıcı ölçüm: iki dal aynı anda hazırsa seçim rastgeledir ve bu durum koşturulmuyor, yalnızca modelleniyor — rastgelelik dilin kendi kararı.
İçindekiler
Önceki ders bir kanalın kapasitesinin engellemeyi nerede başlattığını ölçtü ve select ile
default’u yalnız tek bir kanalı yoklamak için kullandı. Ama bir goroutine çoğu zaman
birden çok kaynaktan aynı anda haber bekler: birkaç sonuç kanalından biri hazır olabilir, bir
iptal sinyali gelebilir, bir zaman aşımı dolabilir. select deyimi bunun için var — birden
çok kanal üzerinde aynı anda bekleyip hangisi önce hazır olursa onu işleyen tek yapı.
Bu ihtiyaç, tek bir kanalı beklemenin yetmediği her yerde ortaya çıkıyor. Bir goroutine
yalnız bir kanalı beklerken <-kanal yazmak yeterlidir; ama iki ayrı kaynaktan hangisi
önce gelirse onu işlemek isteyen bir goroutine, bunu tek bir alım ifadesiyle yazamaz.
select tam olarak bu boşluğu dolduruyor.
Bir select bloğu, her biri bir kanal işlemiyle başlayan birden çok case içerir. Deyim bu
işlemlerden hazır olanlardan birini seçip o dalı çalıştırır; hiçbiri hazır değilse ve bir
default dalı varsa hemen o çalışır, yoksa select en az bir dal hazır olana kadar bekler.
Bu ders bu seçimin nasıl belirlendiğini, nerede belirlenimci nerede rastgele olduğunu ölçüyor.
selectin her dalı bir gönderim ya da bir alım olabilir; aynı select içinde bazı dallar
gönderim, bazıları alım olabilir, önemli olan yalnızca hangisinin o an engellemeden
tamamlanabileceğidir. Bu, selecti bir switchten ayıran ilk fark: switch bir değeri
sırayla dallarla karşılaştırırken, select her dalın kendi kanal işlemini aynı anda
dener ve hangisi hazırsa ona göre karar verir. Bir kanal işlemi olmayan bir case yazmak
derlenmez; selectin dalları her zaman bir gönderim ya da alımla başlar.
Bir selectin kaç dalı olabileceğine dilde bir üst sınır yok; bir select bloğu iki
kanalı da, on kanalı da bekleyebilir. Bu ders yalnızca iki kanallı örnekler kullanıyor,
çünkü ölçülen kurallar — belirlenimli seçim, sıra bağımsızlığı, rastgelelik — dal
sayısından bağımsız; iki dal bu kuralları göstermek için yeterli ve okunması en kolay
durum. Dal sayısı arttıkça ölçülen kurallar değişmiyor, yalnızca aynı anda hazır
olabilecek dal sayısı artıyor; üçüncü bölümdeki rastgelelik kuralı da ikiden fazla dal
hazır olduğunda aynı biçimde geçerli kalıyor.
Tek Dal Hazırken Seçim Belirlenimlidir
Bir select içindeki dallardan yalnız biri hazırsa, select başka hiçbir seçenek
düşünmeden o dalı çalıştırır. Hiçbiri hazır değilse ve bir default dalı varsa, select
hiç beklemeden o dala düşer — önceki derste kapasiteyi yoklamak için kullanılan tam olarak
bu davranıştı.
// tekilVeVarsayilan.go — yalniz bir dal hazirken secim belirlenimli, hicbiri hazir degilken varsayilan dal secilir
package main
import "fmt"
func sec(a, b chan string) string {
select {
case v := <-a:
return "a: " + v
case v := <-b:
return "b: " + v
default:
return "varsayilan: hicbiri hazir degil"
}
}
func main() {
a := make(chan string, 1)
b := make(chan string, 1)
fmt.Println("hicbiri hazir degilken:", sec(a, b))
a <- "ilk deger"
fmt.Println("yalniz a hazirken :", sec(a, b))
b <- "ikinci deger"
fmt.Println("yalniz b hazirken :", sec(a, b))
}
hicbiri hazir degilken: varsayilan: hicbiri hazir degil yalniz a hazirken : a: ilk deger yalniz b hazirken : b: ikinci deger
GK14. — Üç çağrı da aynı sec fonksiyonunu, aynı kaynak koduyla çağırıyor; değişen
tek şey kanalların doluluğu. İlk çağrıda ikisi de boş, default seçiliyor. aya bir
değer konduğunda ikinci çağrı a dalını seçiyor ve bu arada adaki değeri tüketiyor
— a şimdi yine boş. Üçüncü çağrıda yalnız b doluyken b dalı seçiliyor. Hiçbir
çağrıda birden fazla dal aynı anda hazır olmadı; bu yüzden her üç sonuç da kesinlikle
tekrarlanabilir.
Bu ölçümün tek goroutine içinde, hiç eşzamanlılık gerektirmeden yapılabilmesi önemli: a
ve b kanallarına doğrudan ana goroutine’den değer konuluyor, hiçbir goroutine
başlatılmıyor. Bu, selectin kendisinin eşzamanlılığa özgü bir yapı olmadığını gösteriyor
— select yalnız birden çok kanal işlemi arasında seçim yapan bir dildir; kanalların
karşı tarafında goroutine olup olmaması selectin kendi davranışını değiştirmiyor. Sonraki
derslerde select hep goroutine’ler arasında kullanılacak, ama bu ders onun çekirdek
davranışını en yalın hâliyle, tek goroutine’de gösteriyor.
Bu ayrım küçük görünse de önemli: selectin belirlenimli davranışı zamanlamadan değil,
yalnızca hangi kanalların o an dolu olduğundan kaynaklanıyor. Aynı kod, kanalları
farklı bir sırayla doldursaydı ya da farklı bir makinede çalıştırılsaydı sonuç yine aynı
olurdu — çünkü belirleyici olan zamanlama değil, doluluk durumunun kendisi.
Yazım Sırası Bir Öncelik Değildir
selectin dalları yukarıdan aşağı bir öncelik sırası taşımaz — bu, sıradan bir if/else if zincirinden ya da bir switchten temel bir farktır. Hazır olan tek dal hangi sırada
yazılmış olursa olsun seçilir; yazım sırası yalnızca okunabilirlik içindir, çalışma zamanı
davranışını etkilemez.
// siraOnemsiz.go — select dallarinin yazim sirasi hangi dalin secilecegini belirlemez
package main
import "fmt"
func sec(a, b chan string) string {
select {
case v := <-b:
return "b: " + v
case v := <-a:
return "a: " + v
}
}
func main() {
a := make(chan string, 1)
b := make(chan string, 1)
a <- "sadece a doldu"
fmt.Println(sec(a, b))
}
a: sadece a doldu
GK15. — Bu fonksiyonda b dalı kaynakta önce, a dalı sonra yazılı. Yalnız
a doluyken çalıştırıldığında sonuç yine a: sadece a doldu — kaynaktaki sıralamanın
sonucu değiştirmediğini gösteriyor. selectin her casei eşit ağırlıkta değerlendirilir;
hazır olan tek dal neyse o çalışır. Bu, bir sonraki bölümün sorusunu doğuruyor: iki dal
aynı anda hazır olsaydı ne olurdu, madem sıralama bir öncelik vermiyor?
Bu davranış, selectin dallarını değerlendirme biçimiyle ilgili. Kaynakta yazılan sıra
yalnızca derleyicinin caseleri okuma sırasıdır; çalışma zamanında select, çalıştırılacağı
anda dalların tümünü birden değerlendirip hazır olanları bulur, sonra bunlar arasından
seçer. Sıradan bir if a != nil { ... } else if b != nil { ... } zincirinde ilk koşulu
sağlayan dal kazanırdı — orada yazım sırası gerçekten bir önceliktir. select bu modele
uymuyor; bu fark, selecti öğrenirken en sık karışan noktalardan biri, çünkü sözdizimi
switche çok benziyor ama değerlendirme kuralı farklı.
Sınırlayıcı Ölçüm: İki Dal Birden Hazırsa Seçim Rastgeledir
Buraya kadarki her ölçüm, herhangi bir anda en fazla bir dalın hazır olduğu durumları
kurdu. İki dal aynı anda hazır olduğunda dil hangi kuralı uygular? Go’nun kendi belirtimi
bu soruya açık bir yanıt veriyor: birden çok dal aynı anda hazırsa, select aralarından
tekdüze rastgele birini seçer. Bu, kursun bu derse kadarki her ölçümünün dayandığı
belirlenimcilik kuralının bilinçli bir istisnası — ve bu yüzden bu ders bu durumu
koşturmuyor, yalnızca modelliyor.
// modellenir.go — iki dal ayni anda hazirsa secim rastgeledir; bu ders bu dali kosturmaz, yalniz gosterir
package main
func ikiDalBirdenHazirsaSecim(a, b chan string) string {
select {
case v := <-a:
return "a: " + v
case v := <-b:
return "b: " + v
}
}
GK16. — Bu fonksiyon, a ve bnin ikisi de dolu çağrıldığında selectin tam olarak
karşılaştığı durumu gösteriyor: derleyici burada iki dal arasında sabit bir tercih
yapmıyor, çalışma zamanı her çağrıda yeniden rastgele seçiyor. ikiDalBirdenHazirsaSecim
bu dersin hiçbir yerinde çağrılmıyor — çağrılsaydı, iki koşum arasında farklı sonuçlar
üretebilirdi ve bu, kod_dogrula’nın aradığı “her koşumda aynı çıktı” koşulunu bilerek
bozardı. Fonksiyon yalnızca var olduğu için derleniyor; kaynağın kendisi rastgeleliğin
nerede ortaya çıktığını gösteriyor, koşumu değil.
Bu rastgelelik bir eksiklik değil, bilinçli bir tasarım kararı. Eğer select iki hazır
dal arasında hep kaynaktaki ilk dalı seçseydi, programcılar zamanla bu sırayı bir öncelik
kuralı gibi kullanmaya başlar ve kanallardan birinin sistematik olarak açlığa
düşmesine yol açabilirdi — art arda aynı iki dal sürekli hazır kaldığında, ikinci dal
hiç çalışmayabilirdi. Rastgele seçim bu riski dilin kendisi ortadan kaldırıyor: hiçbir
dal, yalnızca kaynakta önce yazıldığı için sistematik olarak öne geçemiyor.
Bu ölçülemezlik, önceki bölümdeki “yazım sırası bir öncelik değildir” bulgusunun asıl gerekçesi. Sıra bir öncelik taşısaydı, iki dal aynı anda hazır olduğunda hangisinin seçileceği yine belirlenimci olurdu — ilk yazılan kazanırdı. Sıranın hiçbir ağırlığı olmadığı için, iki dal birden hazır olduğunda gerçekten seçilecek bir kural kalmıyor; dil bu boşluğu rastgelelikle dolduruyor, M09/K01 Go Temelleri kursunda görülen “tanımlı” sınıfından farklı bir yol: orada yazılmayan bir şeyin yerine sabit bir değer konuyordu, burada yazılmayan bir şeyin yerine her seferinde yeniden çekilen bir değer konuyor. Bu ders bu ayrımı bir eksen olarak sürdürmüyor, yalnız bir kez not ediyor.
Bu ders ikiDalBirdenHazirsaSecimi hiç çağırmadığı için, iki koşum arasındaki farkı
göstermenin tek yolu kaynağı okumak. Bu, kursun M09/K03’e kadarki bütün derslerinin
aksine, “aynı kaynak farklı koşumlarda farklı sonuç verebilir” iddiasının koşturmadan
kanıtlandığı tek yer: rastgeleliğin varlığını Go’nun kendi belirtimi garanti ediyor, bu
ders yalnız o garantinin nerede devreye girdiğini kaynak üzerinde gösteriyor.
select Bloklamayan Bir Denetim Aracıdır
default dalı selecti yalnızca bir yoklama aracına değil, akışı hiç durdurmadan devam
ettiren bir denetim aracına dönüştürüyor. Bu, bir kanaldaki bütün hazır değerleri, hiçbiri
kalmayana kadar tüketmek için kullanılabilir — kanal boşaldığı anda döngü, beklemeden
kendini durdurur.
Bu kullanım, selectin iki farklı işlevini birbirinden ayırıyor. Birinci bölümdeki
kullanımda select birden çok kanal arasında seçim yapıyordu — hangi kaynağın hazır
olduğunu belirliyordu. Burada select tek bir kanal üzerinde, o kanalın hazır olup
olmadığını sormak için kullanılıyor; default dalının varlığı, “hazır değilse bekleme”
demenin dildeki karşılığı. İki kullanım da aynı sözdizimini paylaşıyor, ama amaçları
farklı: biri birden çok kaynak arasında karar veriyor, öteki tek bir kaynağın anlık
durumunu okuyor.
// engellenmeyenDenetim.go — select, hazir olmayan bir kanali beklemeden akisi surdurmek icin kullanilir
package main
import "fmt"
func hazirsaAl(kanal chan int) (int, bool) {
select {
case v := <-kanal:
return v, true
default:
return 0, false
}
}
func main() {
sonuclar := make(chan int, 3)
sonuclar <- 10
sonuclar <- 20
toplam, adet := 0, 0
for {
v, tamam := hazirsaAl(sonuclar)
if !tamam {
break
}
toplam += v
adet++
}
fmt.Println("kanaldan engellenmeden alinan deger sayisi:", adet, "toplam:", toplam)
_, tamam := hazirsaAl(sonuclar)
fmt.Println("kanal bosalinca uzerine deneme :", tamam)
}
kanaldan engellenmeden alinan deger sayisi: 2 toplam: 30 kanal bosalinca uzerine deneme : false
GK17. — sonuclar kanalına yalnızca iki değer konuldu; döngü bu ikisini hazirsaAl
ile tüketiyor ve üçüncü denemede tamam false döndüğü için döngü kendiliğinden
duruyor — hiçbir goroutine beklenmedi, hiçbir sıra çizelgeleyiciye bırakılmadı. Bu
düzenek, bir kanalın “şu an elimde ne var” diye sorulmasını sağlıyor; kanalın kendisi
boşsa, soran taraf beklemeden yoluna devam edebiliyor. Bu, önceki derste tampon
kapasitesini yoklamak için kullanılan tekniğin genel biçimi: orada tek bir kanalın dolu
olup olmadığı soruluyordu, burada aynı teknik bir kanalı tüketmek için kullanılıyor.
Döngünün durma koşulu da dikkat çekici: for döngüsü sabit bir sayıya göre değil,
hazirsaAlin döndürdüğü ikinci değere göre duruyor. Kaynak koddan kaç kez döneceği
okunamaz — sonuclar kanalına kaç değer konulmuş olursa olsun aynı döngü çalışır ve
tam olarak o kadar adımda durur. Bu, range ile bir dilim gezmekten farklı: rangein
kaç kez döneceği dilimin uzunluğundan önceden okunabilirken, bu döngünün kaç kez
döneceği yalnızca koşarak öğrenilir. Bu fark, kapanış dersinin ölçtüğü range over channel davranışıyla karşılaştırıldığında daha da netleşiyor: range bir kanalın
kapanmasını bir durma koşulu sayarken, buradaki döngü kanalın o anki doluluğunu
bir durma koşulu sayıyor — ikisi de sonlu bir döngü üretir, ama farklı bir sinyale göre.
Bu kullanım biçiminin bir bedeli var: hazirsaAl bir goroutine’in üretmesini beklemez,
yalnızca o anda hazır olanı alır. Kanala henüz hiçbir şey yazılmamışsa, hazirsaAl her
zaman false döner — üretici goroutine bir mikrosaniye sonra bir değer gönderecek olsa
bile. Bloklamayan bir denetim aracı, karşılığında beklemeyi de kaybeder; hangisinin
gerektiği, ne zaman selectin beklemesine izin verileceği, ne zaman hemen devam etmesi
gerektiğine bağlı bir tasarım kararı.
Bu tehlike, default dalını bir döngü içine koymanın yanlış kullanımıyla daha da
belirginleşiyor: eğer amaç bir değerin gelmesini beklemekse ve döngü default dalına her
düştüğünde hemen tekrar denerse, program hiçbir zaman bloklanmadan, işlemciyi sürekli
meşgul ederek bekler — bu, kilit ve kritik bölge bağlamında M01/K05 İşletim Sistemi
Kavramları kursunda kurulmuş meşgul bekleme kalıbının select üzerindeki karşılığı.
Bu ders o kalıbı kurmuyor: hazirsaAl bir kez çağrılıyor, bir döngüde beklenmiyor.
Beklemek gerektiğinde doğru araç defaultsuz bir selecttir — o zaman select kendisi
hazır bir dal olana kadar bloklanır ve işlemciyi meşgul etmez; bu, birinci bölümdeki
sec fonksiyonunun default dalı olmadan yazılmış hâli olurdu.
Özet
select, birden çok kanal işleminden yalnızca bir tek dalın hazır olduğu durumda o dalı belirlenimci biçimde seçer.- Dalların kaynaktaki yazım sırası bir öncelik taşımaz; hazır olan dal hangi sırada yazılmış olursa olsun seçilir.
- Hiçbir dal hazır değilken bir
defaultdalı varsa hemen çalışır;selectbu şekilde hiç beklemeyen bir yoklama ve denetim aracına dönüşür. - İki dal aynı anda hazır olduğunda seçim tekdüze rastgeledir; bu kursun ölçtüğü belirlenimcilik kuralının bilinçli istisnasıdır ve bu yüzden koşturulmaz, yalnız modellenir.
select+defaultbir kanalı, hiçbir goroutine beklemeden, elindeki değerler bitene kadar tüketmek için kullanılabilir.defaultsuz birselecthiçbir dal hazır olmayınca bloklanır ve işlemciyi meşgul etmez; beklemek gerektiğinde doğru araç budur,defaultlı bir döngü değil.
Sonraki Adım
Bu derste kapanan bir kanalın select içindeki davranışına hiç değinilmedi. Kapanmış bir
kanaldan alım da tıpkı dolu bir kanaldan alım gibi bir select dalını hemen hazır kılar —
ama gelen yanıt, ölçülen değerin kendisi değil, kanalın artık boş kalacağının haberi.
Sıradaki ders bu son davranışı ölçüyor: bir kanalın kapatılması bir sonlanma sinyalidir, ve
bu sinyal hem range hem de doğrudan alımda kendini nasıl gösteriyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.