Göndermek ve almak
Channel, goroutine'ler arasında tipli bir borudur. ch <- v gönderir, <-ch alır. make ile bir tane oluşturun:
Bir channel tipinin sıfır değeri nil'dir, bu yüzden make olmadan var ch chan string size sonsuza kadar bloklanan bir channel verir. Channel'ları her zaman make ile oluşturun.
Buffer'sız channel'lar senkronize eder
make(chan T) buffer'sız bir channel oluşturur. Bir gönderim, bir alıcı değeri alana kadar; bir alım da bir gönderici bir değer sağlayana kadar bloklanır. İki goroutine o noktada buluşur; bu da buffer'sız bir channel'ı bir veri borusu olduğu kadar bir senkronizasyon aracı yapar. Aşağıda <-done döndüğünde worker'ın gönderiminden önceki her şeyi bitirdiğini bilirsiniz:
main içinde result'ı okumak burada bir mutex olmadan güvenlidir. Go'nun bellek modeli, worker'ın gönderimden önce yaptığı her şeyin eşleşen alımdan sonra main'e görünür olduğunu garanti eder. struct{} hiç bellek kaplamadığı için saf bir sinyal için deyimsel tip chan struct{}'dir.
Buffer'lı channel'lar
make(chan T, n) channel'a n değerlik yer verir. Gönderimler buffer dolana kadar bir alıcı olmadan başarılı olur; alımlar buffer boşalana kadar başarılı olur. Değerler girdikleri sırayla çıkar.
Bir buffer göndericiyi ve alıcıyı birbirinden ayırır, böylece kısa patlamalar göndericiyi durdurmaz. Tüketicisinden kalıcı olarak daha hızlı olan bir üreticiyi düzeltmez; yalnızca göndericinin bloklandığı anı geciktirir. Bir buffer boyutunu bir deadlock'u yok etmek için değil, bir nedenle (gönderici sayısı, bilinen bir parti boyutu) seçin.
len(ch) bir anlık görüntüdür. Ona göre davrandığınızda başka bir goroutine onu değiştirmiş olabilir, bu yüzden bir gönderimin bloklanıp bloklanmayacağına karar vermek için onu kullanmayın. Bunun için default case'li bir select kullanın.
Close ve range
close(ch) alıcılara başka değer gönderilmeyeceğini söyler. Bir close'tan sonra:
- buffer'da zaten olan değerler yine teslim edilir,
- sonra her alım hemen sıfır değeri döndürür,
v, ok := <-ch,ok == falsebildirir,for v := range chbiter.
close'tan sonraki ilk alım yine ok == true ile "last"'ı alır. İkincisi sıfır değer ""'yi ve false'u alır.
Panic'e yol açan kurallar:
- kapatılmış bir channel'a göndermek
send on closed channelile panic olur, - zaten kapatılmış bir channel'ı kapatmak panic olur,
- bir
nilchannel'ı kapatmak panic olur.
Bu yüzden yalnızca gönderen taraf kapatır, ve yalnızca bir kez. Birden fazla göndericiyle hiçbir gönderici diğerlerinin ne zaman bittiğini bilmez; tüm göndericileri (bir sync.WaitGroup ile) bekleyen ayrı bir goroutine'iniz olsun ve channel'ı Wait döndükten sonra kapatsın. Kapatmak yalnızca bir alıcı sonu beklediğinde gereklidir. Kimsenin başvurmadığı kapatılmamış bir channel diğer değerler gibi çöp toplayıcı tarafından toplanır.
Yön tipleri
Bir fonksiyon bir channel'a yalnızca gönderdiğini ya da yalnızca ondan aldığını bildirebilir. Derleyici o zaman diğer işlemi reddeder.
| Tip | Anlamı | İzin verilen |
|---|---|---|
chan T | çift yönlü | gönder, al, kapat |
chan<- T | yalnızca gönderen | gönder, kapat |
<-chan T | yalnızca alan | al |
Bir chan T, bir fonksiyona aktardığınızda kısıtlı tiplerden herhangi birine örtük olarak dönüşür. Yukarıdaki produce'un bir <-chan int döndürebilmesinin nedeni budur: çağıranlar onun üzerinde range yapabilir ama ona gönderemez ya da onu kapatamaz. Uygun olan her fonksiyon parametresinde yön tiplerini kullanın. Sahipliği belgelerler ve yanlış kullanımı bir derleme hatasına dönüştürürler.
Deadlock
Her goroutine bloklanmışsa ve hiçbir şey hiçbirini uyandıramıyorsa, çalışma zamanı programı durdurur:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/main.go:7 +0x38
exit status 2
Goroutine dökümü hangi işlemin takıldığını söyler (burada chan send, ya da chan receive, sync.WaitGroup.Wait, select). Olağan nedenler:
- hiçbir alıcı çalışmıyorken buffer'sız bir channel'a gönderim,
- hiç kapatılmayan bir channel üzerinde
range, - hiç sıfıra ulaşmayan bir
WaitGroupsayacı, - birbirini bekleyen iki goroutine.
Çalışma zamanı yalnızca tüm goroutine'lerin takıldığı durumu tespit eder. Başka goroutine'lerin yaşadığı bir sunucuda (bir HTTP dinleyicisi, bir ticker) aynı hata hiçbir şeyi çökertmez; bloklanan goroutine yalnızca sızar.
Nil channel'lar
Bir nil channel'a gönderimler ve ondan alımlar sonsuza kadar bloklanır. Bu işe yaramaz gibi görünür ama select içinde standart bir hiledir: bir channel değişkenini nil yapmak onun case'ini devre dışı bırakır. Bu, iki channel'ı birleştirir ve her biri kapandığında onu dinlemeyi bırakır:
nil atamaları olmadan kapatılmış bir channel her zaman hazırdır ve döngü sıfır değerler üzerinde boşa dönerdi.
Bir pipeline
Channel'lar pipeline'lara dönüşür: her aşama bir channel'dan alıp bir sonrakine gönderen ve girdisi bittiğinde çıktısını kapatan bir goroutine'dir.
Her aşama eşzamanlı çalışır ve çıktı sırası deterministiktir (1, 16, 81), çünkü her aşama sırayı koruyan tek bir goroutine'dir. Kapatmalar zincirleme ilerler: generate kapatır, bu square'in range'ini bitirir, o da çıktısını kapatır ve bu main'e kadar böyle gider.
Bu pipeline'ın zayıf noktası: main okumayı erken bıraksaydı aşamalar gönderimlerinde sonsuza kadar bloklanırdı. Gerçek pipeline'lar bir context.Context ya da bir done channel'ı alır ve her gönderimin yanında onun üzerinde select yapar.
Channel mı, mutex mi
Channel'lar verinin sahipliğini aktarmak ve olayları bildirmek içindir. Bir sync.Mutex, bir önbellek ya da bir sayaç gibi birçok goroutine'in yerinde okuyup güncellediği paylaşılan durumu korumak için daha basittir. İçinde mutex olan bir struct, durumun sahibi olup channel'lar üzerinden istekleri karşılayan bir goroutine'den çoğu zaman daha anlaşılırdır. Hangisi kodu kısaltıyor ve sahipliği belirgin yapıyorsa onu kullanın.
Hızlı başvuru
| İşlem | nil channel | açık channel | kapatılmış channel |
|---|---|---|---|
ch <- v | sonsuza kadar bloklanır | alınana ya da buffer'da yer açılana kadar bloklanır | panic olur |
<-ch | sonsuza kadar bloklanır | bir değer gelene kadar bloklanır | buffer'daki değerler, sonra sıfır değer |
v, ok := <-ch | sonsuza kadar bloklanır | ok true | boşaldıktan sonra ok false |
close(ch) | panic olur | kapatır | panic olur |
len(ch), cap(ch) | 0, 0 | buffer'daki değerler, buffer boyutu | kalan değerler, buffer boyutu |
Sıkça Sorulan Sorular
Go'da buffer'lı ve buffer'sız channel arasındaki fark nedir?
Buffer'sız bir channel'ın (make(chan int)) depolaması yoktur: bir gönderim başka bir goroutine alana kadar bloklanır, bu yüzden her gönderim aynı zamanda bir teslim ve bir senkronizasyon noktasıdır. Buffer'lı bir channel (make(chan int, 3)) en fazla 3 değer tutar; gönderimler yalnızca buffer doluyken, alımlar yalnızca boşken bloklanır.
Go'da kapatılmış bir channel'dan okuyunca ne olur?
Kapatılmış bir channel'dan alımlar asla bloklanmaz. Önce buffer'da hâlâ duran değerleri boşaltırlar, sonra sonsuza kadar eleman tipinin sıfır değerini döndürürler. Farkı anlamak için v, ok := <-ch kullanın: channel kapanıp boşaldığında ok false olur. Bir for v := range ch döngüsü o noktada durur.
Go'da bir channel'ı kim kapatmalı?
Gönderici, ve yalnızca alıcıların başka değer gelmeyeceğini bilmesi gerektiğinde (örneğin bir range döngüsünü bitirmek için). Kapatılmış bir channel'a göndermek panic'e yol açar, bir channel'ı iki kez kapatmak da; bu yüzden channel'ı kapatan bir alıcı göndericilerle race'e girer. Bir channel'ı serbest bırakmak için kapatmanız gerekmez; çöp toplayıcı erişilemeyen channel'ları her iki durumda da geri alır.
"fatal error: all goroutines are asleep" ne anlama gelir?
Programdaki her goroutine, hiçbir şeyin asla tamamlayamayacağı bir channel işlemi ya da kilit üzerinde bloklanmış durumdadır, bu yüzden çalışma zamanı programı durdurur. En yaygın neden, başka hiçbir goroutine almıyorken main içinde buffer'sız bir channel'a göndermek ya da hiç kapatılmayan bir channel üzerinde range yapmaktır.