Ders 05 / 14
Kanal Kapatma ve Aralık
Bir kanalın kapatılması bir sonlanma sinyalidir: kapanan kanalda range biter, kapanan kanaldan doğrudan alım sıfır değer ve false döner. Dört alım biçiminin (açık, kapalı-dolu, kapalı-boş, tekrar kapalı-boş) yanıtı bunu ölçüyor. Kapatma sorumluluğu her zaman göndericidedir. Sınırlayıcı ölçüm: kapalı bir kanala gönderim de, bir kanalı ikinci kez kapatmak da panikler; ikisi de recover ile yakalanıp yalnız panik=true/false olarak yazılıyor.
İçindekiler
Önceki ders selectin belirlenimli seçimini ve rastgele istisnasını ölçtü, ama bir soruyu
açık bıraktı: kapanmış bir kanal select içinde nasıl davranır? Bu ders bu soruyla açılıyor
ve konunun son sorusuna geçiyor — bir kanalın kapatılması neyi bildiriyor, ve bu bildirim
kimin sorumluluğunda. Bu, konunun beşinci ve son dersi: goroutine’lerin nasıl durduğu, kanalların ne
aktardığı, tamponun neyi ertelediği ve selectin nasıl seçtiği ölçüldü; geriye tek bir
soru kaldı — bir iletişim ne zaman biter, ve bu bitişi kim ilan eder.
Bir kanal close(kanal) ile kapatılabilir. Kapatma bir değer göndermez; kanalın artık
hiçbir yeni değer taşımayacağını bildiren ayrı bir sinyaldir. Bu sinyal iki yerde
görünür: doğrudan bir alımda (deger, tamam := <-kanal) ve bir range döngüsünde. Bu
dersin ölçtüğü şey, bu iki görünümün tam olarak ne söylediği. close yalnızca kanallar
üzerinde tanımlıdır; başka hiçbir tip için karşılığı yoktur, çünkü bir kanalın “artık boş
kalacak” demenin gerçek bir karşılığı vardır, oysa bir tam sayının ya da bir dizginin
böyle bir sonlanma kavramı hiç yoktur.
Kapatma, bu kursun ilk dersinde sayılan dört durma biçiminden birine doğrudan bağlanıyor:
“beklediği geldi” sınıfının bir goroutine’i, beklediği şeyin bir kanal kapatması olduğunda,
kapatma anında serbest kalıyor. O derste kapanan bir kanalda range yapan bir goroutine
tam olarak bu yolla durmuştu; bu ders o tek satırlık gözlemi genişletiyor. Aynı dersteki
“hiç durmadı” örneklerinden biri de kapatılmayan bir kanaldı — bu ders o örneğin neden
hiç durmadığını da ayrıca ölçüyor.
Kapanan Kanal select İçinde Hemen Hazırdır
Önceki dersin sonunda bırakılan soru buydu: kapanmış bir kanal, hiç değer içermese bile,
bir select dalını hazır kılar mı? Yanıt evet — kapanmış bir kanaldan alım hiçbir zaman
engellemez, bu yüzden o dal her zaman seçilmeye hazırdır. Taşıdığı yanıt ise bir değer
değil, kanalın bittiğinin haberidir.
// selectKapali.go — kapanan bir kanal select icinde hemen hazir olur, sifir deger ve false doner
package main
import "fmt"
func main() {
kapali := make(chan int)
close(kapali)
bekleyen := make(chan int)
select {
case v, tamam := <-kapali:
fmt.Println("kapali kanaldan gelen dal secildi, deger=", v, "tamam=", tamam)
case v, tamam := <-bekleyen:
fmt.Println("beklenen kanaldan gelen dal secildi, deger=", v, "tamam=", tamam)
}
}
kapali kanaldan gelen dal secildi, deger= 0 tamam= false
GK18. — bekleyen hiçbir zaman doldurulmuyor ve hiçbir zaman kapatılmıyor; bu dal
sonsuza kadar hazır olmayacaktı. kapali ise close çağrısından itibaren her zaman
hazırdır — bu yüzden select bu iki dal arasında hiç tereddüt etmeden kapali dalını
seçiyor, önceki dersteki rastgelelik burada devreye girmiyor çünkü yalnız bir dal gerçekten
hazır. v sıfır değerinde (0) kalıyor ve tamam false dönüyor — bu iki yanıtlı biçim,
M09/K01 Go Temelleri kursunda sözlük erişiminde görülen aynı sözdiziminin kanallardaki
karşılığı, ama taşıdığı anlam farklı: sözlükte tamam bir anahtarın var olup olmadığını
söylerken, kanalda kanalın açık olup olmadığını söylüyor.
Bu, önceki dersteki nil kanal gözlemiyle de bağlantılı: bir select içinde bir dalı
etkisiz kılmanın yolu o dalın kanalını nile çevirmekti, çünkü nil bir kanal hiçbir
zaman hazır olmuyordu. Kapalı bir kanal bunun tam tersi uçta duruyor — hiçbir zaman hazır
olmamak yerine, her zaman hazır. İkisi de birer sınır durum, ama zıt yönlerde: biri
sonsuza kadar bekletir, öteki hiç beklemez.
Bu iki sınır durum birlikte kullanılabiliyor da: bir goroutine, bir işi bitirdiğinde
kendi ilerleme kanalını nile çevirip o daldan artık hiç seçilmemesini sağlayabilir, ya
da bir iptal kanalını kapatıp tüm bekleyenlerin aynı anda uyanmasını sağlayabilir. Bu
kalıpların ikisi de B payının konusu; burada yalnız uçların kendisi ölçüldü.
Dört Alım Biçimi
Kapatmanın taşıdığı bilgi, bir kanalın o an hangi durumda olduğuna göre değişiyor. Dört durum birbirinden ayrılıyor: kanal açıkken bir değer alınıyor, kanal kapatıldıktan sonra tamponda kalan bir değer alınıyor, tampon da boşaldıktan sonra alınıyor, ve bu son durum tekrarlanıyor.
// dortAlim.go — kapali kanaldan alimin dort bicimi: acik, kapali-dolu, kapali-bos, araliktan cikma
package main
import "fmt"
func main() {
kanal := make(chan int, 2)
kanal <- 10
kanal <- 20
v1, ok1 := <-kanal
fmt.Println("acikken alim :", v1, ok1)
close(kanal)
v2, ok2 := <-kanal
fmt.Println("kapali ama tamponda var :", v2, ok2)
v3, ok3 := <-kanal
fmt.Println("kapali ve bos :", v3, ok3)
v4, ok4 := <-kanal
fmt.Println("tekrar kapali ve bos :", v4, ok4)
toplam, adimSayisi := 0, 0
kanal2 := make(chan int, 3)
kanal2 <- 1
kanal2 <- 2
kanal2 <- 3
close(kanal2)
for deger := range kanal2 {
toplam += deger
adimSayisi++
}
fmt.Println("range ile toplam ve adim:", toplam, adimSayisi)
}
acikken alim : 10 true kapali ama tamponda var : 20 true kapali ve bos : 0 false tekrar kapali ve bos : 0 false range ile toplam ve adim: 6 3
GK19. — İlk alım kanal daha kapatılmadan önce yapılıyor ve olağan bir alım gibi
true dönüyor. close(kanal) çağrıldığında tamponda hâlâ bir değer (20) duruyor;
kapatma bu değeri silmiyor — ikinci alım onu true ile veriyor. Tampon boşaldıktan
sonraki üçüncü alım artık sıfır değer (0) ve false veriyor; dördüncü alım da aynı
yanıtı tekrarlıyor, çünkü kapalı ve boş bir kanaldan alım sonsuza kadar bu yanıtı
verir, bir daha asla bloklanmaz. range ise bu davranışı bir döngüye çeviriyor: kanal
kapandığında ve tamponu boşaldığında döngü kendiliğinden bitiyor, break yazmaya gerek
kalmıyor — Denetim Akışı dersinde M09/K01’de görülen “yazılmayan break” kalıbının
kanallardaki karşılığı.
Bu dört yanıt, kapatmanın neyi bildirdiğini tam olarak gösteriyor: kapatma var olan değerleri geçersiz kılmıyor, yalnızca yeni değer gelmeyeceğini bildiriyor. Tamponda bekleyen değerler kapatmadan sonra da normal şekilde alınabiliyor; sinyal yalnız tampon tükendiğinde devreye giriyor.
Bu dört durumun sırayla ölçülmüş olması önemli: ölçüm hiçbir goroutine başlatmadan, tek bir
goroutine içinde, adım adım kuruluyor. Kapatma anında tamponda kaç değer kaldığı önceden
biliniyor (kanal <- 10 ve kanal <- 20 iki değer koydu, yalnız biri alındı), bu yüzden
“kapalı ama tamponda var” durumunun tam olarak bir kez görüneceği baştan kesin. Aynı ölçüm
tamponsuz bir kanalda yapılsaydı ikinci durum (kapalı ama tamponda değer var) hiç
gerçekleşmezdi — tamponsuz bir kanalda kapatma anında bekleyen bir değer olamaz, çünkü her
gönderim ancak bir alıcı hazır olduğunda tamamlanıyordu.
Kapatma Sorumluluğu Göndericide Olur
Bir kanalı kapatma yetkisi teknik olarak hem gönderen hem alan tarafından çağrılabilir —
close bir kanal değerini alan her taraf tarafından çalıştırılabilir. Ama bu yetkinin
doğru kullanımı tektir: yalnızca gönderen taraf kapatabilir, çünkü yalnız gönderen
tarafın “artık gönderecek değerim kalmadı” bilgisi vardır. Bir alıcının kanalı kapatması,
göndericinin elinde hâlâ göndermeyi planladığı bir değer varken kanalı kapatmış olması
riskini taşır — ve kapalı bir kanala gönderim panikler, bu dersin son bölümünün konusu.
Bu kural, kanalın kendi tipinde yazılı değil. chan<- int ile kısıtlanmış bir parametre
bile close çağırabilir — yön kısıtlaması yalnızca gönderim ile alımı ayırıyordu, kapatma
ikisinden de ayrı bir işlem ve her iki yönde de tanımlı. Kuralın kaynağı dil değil,
bilgi: yalnızca göndericinin ne zaman göndermeyi bitireceğini bilme ihtimali var,
alıcının böyle bir bilgisi hiçbir zaman olamaz. Bir alıcı, kendisine daha gönderim
yapılıp yapılmayacağını kaynak koddan asla kesin biçimde bilemez.
Kapatmanın hiç yapılmamasının da bir bedeli var. Kapatılmayan bir kanalda bekleyen bir
range, kanal hiçbir zaman kapatılmadığı sürece hiçbir zaman durmaz — M09/K03’ün ilk
dersinde ölçülen “hiç durmadı” sınıfının burada tekrar görünen hâli. O derste bu sınıfa
giren üç örnekten biri tam olarak buydu: kapanmayan bir kanalda range yapan bir
goroutine, programın geri kalanı bitse bile kendisi hiç bitmiyordu.
// kapatilmayanRisk.go — kapatilmayan bir kanalda range hic durmaz, sizinti boolean olarak gorunur
package main
import (
"fmt"
"runtime"
)
func main() {
taban := runtime.NumGoroutine()
kapanmayan := make(chan int)
basladi := make(chan struct{})
go func() {
close(basladi)
for range kapanmayan {
}
}()
<-basladi
fmt.Println("taban sayidan fazla goroutine var mi:", runtime.NumGoroutine() > taban)
}
taban sayidan fazla goroutine var mi: true
GK20. — basladi kanalı yalnızca goroutine’in başladığını doğrulamak için var; asıl
ölçülen kapanmayan üzerindeki rangein hiç bitmediği. Bu goroutine, kapanmayan
kapatılmadığı için, program sonlanana kadar bellekte kalıyor. runtime.NumGoroutinein
verdiği sayı hiçbir yerde basılmıyor, yalnızca tabanla karşılaştırılıp true/falseya
indirgeniyor — kursun ilk dersinde kurulan kural burada aynen sürüyor. Bu ölçüm,
kapatmanın isteğe bağlı bir incelik değil, bekleyen taraf varsa bir zorunluluk
olduğunu gösteriyor.
Bu ölçümün doğruluğu iki şeye dayanıyor. Birincisi, <-basladi alımı, goroutine’in en
azından close(basladi) satırına ulaştığını garanti ediyor — bu da goroutine’in henüz
dönmediğini, dolayısıyla hâlâ sayılabilir olduğunu gösteriyor. İkincisi, goroutine’in
gövdesinde close(basladi)den sonra tek bir satır var (for range kapanmayan) ve bu
satır, kapanmayan hiç kapatılmadığı için asla bitmiyor; goroutine’in <-basladi
alımından sonra dönmüş olma ihtimali yok. Bu iki gözlem birlikte, runtime.NumGoroutinein
her koşumda aynı yanıtı (true) vermesini garanti ediyor — burada da hiçbir sıra
çizelgeleyiciye bırakılmadı.
Sınırlayıcı Ölçüm: Kapalı Kanala Gönderim ve İkinci Kapatma Panikler
Kapatmanın kendi kuralları var, ve bu kurallar ihlal edildiğinde dil sessiz kalmıyor.
Kapalı bir kanala gönderim yapmaya çalışmak panikler; aynı kanalı ikinci kez kapatmaya
çalışmak da panikler. Bu iki davranış, recover ile yakalanıp yalnızca panik=true/
panik=false olarak yazılıyor — yığıt izi ve hata metni basılmıyor.
// kapatmaSorumlulugu.go — kapali kanala gonderim ve iki kez kapatma panikler, recover ile yakalanir
package main
import "fmt"
func panikliyorMu(f func()) (p bool) {
defer func() {
if recover() != nil {
p = true
}
}()
f()
return false
}
func main() {
kanal := make(chan int, 1)
close(kanal)
gonderimPanikledi := panikliyorMu(func() { kanal <- 1 })
fmt.Println("kapali kanala gonderim panik=", gonderimPanikledi)
ikinciKapatmaPanikledi := panikliyorMu(func() { close(kanal) })
fmt.Println("kanali ikinci kez kapatmak panik=", ikinciKapatmaPanikledi)
}
kapali kanala gonderim panik= true kanali ikinci kez kapatmak panik= true
GK21. — panikliyorMu, çağırdığı işlevi bir defer/recover çifti içinde
çalıştırıyor ve yalnız panikleyip panikelemediğini bir bool olarak döndürüyor —
M09/K01 Go Temelleri kursunda kurulan panik/kurtarma kalıbının aynısı. defer içindeki
işlev recover()ı çağırıyor; bir panik varsa recover onu durdurup değerini döndürüyor
ve p değişkenini true yapıyor, panik yoksa recover nil dönüyor ve p adlandırılmış
dönüş değeri olarak baştaki falseunda kalıyor. kanal bir değerle
dolu bile olsa (kapasitesi 1, ama içi boş burada) kapatıldıktan sonra her gönderim
panikliyor; bu, üçüncü bölümdeki “kapatma değerleri silmez” gözlemiyle çelişmiyor —
kapatma yalnızca yeni göndermeyi yasaklıyor, var olanı okumayı değil. İkinci kapatma
da aynı şekilde panikliyor, çünkü close bir kanalın durumunu geri döndürülemez
biçimde değiştiriyor; kapalı bir kanalı “tekrar kapatmak” diye bir işlem dilde tanımlı
değil.
Bu iki panik birlikte, kapatma sorumluluğunun neden tek bir tarafta toplanması gerektiğini gösteriyor. İki ayrı goroutine aynı kanalı kapatmaya çalışırsa, ikincisi panikler. İki ayrı goroutine aynı kanala gönderim yapıyorsa ve biri kanalı kapatırsa, ötekinin bir sonraki gönderimi panikler. Kapatmanın kimin işi olduğu konusunda bir anlaşma olmadan birden çok goroutine aynı kanalı paylaşamıyor — bu sorunun kendisi B payının ilk konusu.
Bu iki panik arasında da bir fark var. Kapalı kanala gönderim panikleri, kaynak kodun
her koşumda aynı yerde, aynı koşullarda tetiklenir — closedan sonra bir gönderim
varsa panik kesindir. İkinci kapatma panikleri ise genellikle iki ayrı goroutine’in aynı
kanalı kapatmaya çalıştığı bir yarış durumunun sonucudur; hangi goroutine’in önce
kapattığı belirlenimli olmayabilir, ama en az birinin panikleyeceği kesindir. Bu ders
ikinci durumu da tek bir goroutine içinde, art arda iki close çağrısıyla, belirlenimci
biçimde kuruyor — gerçek bir yarış koşturmadan aynı sonucu gösteriyor.
Panik burada bir hata gösterimi değil, bir sözleşme ihlali gösterimi. Kapalı bir
kanala gönderim ya da bir kanalı ikinci kez kapatmak, çalışma zamanı hatası değil,
programın kendi mantığının bir kanalın durumunu yanlış izlediğinin kanıtıdır. Bu ikisi
recover ile yakalanabiliyor olsa da, kursun kuralı hâlâ geçerli: panik metnini basmak
yerine yalnızca true/false yazılıyor, çünkü panik metni sürüme bağlı bir ayrıntı.
Özet
- Kapanmış bir kanal
selectiçinde her zaman hazırdır; taşıdığı yanıt sıfır değer vefalse, bir veri değil bir sonlanma haberidir. - Kapatma var olan değerleri silmez: tamponda bekleyen değerler kapatmadan sonra da
normal şekilde alınabilir, yalnız tampon tükendiğinde sıfır değer ve
falsebaşlar. range, kanal kapanıp tamponu boşaldığında kendiliğinden durur; ayrıca birbreakyazmaya gerek yoktur.- Kapatma sorumluluğu göndericidedir; kapatılmayan bir kanalda bekleyen bir
rangehiçbir zaman durmaz ve bu, ölçülebilir bir goroutine sızıntısıdır. - Kapalı bir kanala gönderim ve bir kanalı ikinci kez kapatmak panikler; ikisi de geri döndürülemez bir durum değişikliğinin ihlalidir.
- Kapatma yetkisi kanalın tipinde değil bilgide oturur: yalnızca göndericinin ne zaman göndermeyi bitireceğini bilme ihtimali vardır, alıcının hiçbir zaman yoktur.
Sonraki Adım
Bu konunun beş dersi tek bir goroutine ile tek bir goroutine arasındaki iletişimi ölçtü:
bir kanal, iki taraf, bir gönderici, bir alıcı. Her ders bu ikili ilişkinin bir yönünü
ölçtü — goroutine’in kendisi, kanalın iki işi, tamponun ertelemesi, selectin seçimi,
kapatmanın sinyali — ve her biri kursun açılışındaki soruya, bir goroutine’in nasıl
duracağına, bir parça yanıt ekledi.
Kapatma bir sonlanma sinyalidir ve sorumluluğu göndericidedir — ama gönderici tek değilse, ya da alıcı tek değilse, bu sorumluluk nasıl paylaşılır? Bir kanalı birden çok goroutine aynı anda kapatmaya çalışırsa ikincisi panikler; bu dersin son ölçümü bunu gösterdi. Sıradaki konu bu sinyali çok sayıda goroutine arasında kullanan kalıplara bakıyor: işçi havuzları, yayma ve toplama, boru hatları — ve her birinde, kapatma sorumluluğunun kaç taraf arasında nasıl paylaşıldığı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.