Menu

Golang Channel'lar: Buffer'lı, Buffer'sız, Close ve Range

Go channel'ları goroutine'ler arasında değerleri nasıl taşır: buffer'sız ve buffer'lı channel'lar, kapatma ve range, yön tipleri, deadlock hatası ve bunlardan kurulan bir pipeline.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

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 == false bildirir,
  • for v := range ch biter.

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 channel ile panic olur,
  • zaten kapatılmış bir channel'ı kapatmak panic olur,
  • bir nil channel'ı 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.

TipAnlamıİzin verilen
chan Tçift yönlügönder, al, kapat
chan<- Tyalnızca gönderengönder, kapat
<-chan Tyalnızca alanal

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 WaitGroup sayacı,
  • 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

İşlemnil channelaçık channelkapatılmış channel
ch <- vsonsuza kadar bloklanıralınana ya da buffer'da yer açılana kadar bloklanırpanic olur
<-chsonsuza kadar bloklanırbir değer gelene kadar bloklanırbuffer'daki değerler, sonra sıfır değer
v, ok := <-chsonsuza kadar bloklanırok trueboşaldıktan sonra ok false
close(ch)panic olurkapatırpanic olur
len(ch), cap(ch)0, 0buffer'daki değerler, buffer boyutukalan 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.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA