Historia pytania: Kanały są jedną z podstawowych koncepcji modelu CSP w Go. Są przeznaczone do wymiany danych między gorutynami. Praca z kanałami wymaga szczególnej ostrożności, aby uniknąć deadlocków, wycieków i paniki podczas zamykania.
Problem: Zamykanie i dalsze korzystanie z kanałów często prowadzi do paniki (panic: send on closed channel). Praca z kanałami nil lub próby odczytu z zamkniętego kanału bez sprawdzenia mogą dać nieoczekiwany wynik. Różne gorutyny mogą jednocześnie pisać i czytać z jednego kanału, co wymaga jasno określonej logiki cyklu życia.
Rozwiązanie: Kanał zawsze powinien być zamykany tylko z tej strony, z której nikt więcej nie będzie pisał do kanału (zwykle produkujący). Po zamknięciu z kanału można odczytywać wartości, aż się wyczerpią, ale nie można pisać - nastąpi panic. Wygodnie jest sprawdzać zamknięcie poprzez drugi zwracany parametr z kanału:
Przykład kodu:
ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i } close(ch) }() for v := range ch { fmt.Println(v) }
Kluczowe cechy:
1. Czy należy zamykać kanał, jeśli odczytuje go tylko jedna gorutyna i nikt więcej nie oczekuje dodatkowych wartości?
Odpowiedź: Zamykanie kanału nie jest konieczne, jeśli nikt nie czeka na jego zamknięcie przez range. Ale jeśli użyto pętli for v := range ch - tak, kanał należy zamknąć, w przeciwnym razie pętla się nie zakończy.
2. Co się stanie, jeśli będziemy czytać z zamkniętego kanału?
Odpowiedź: Wartości będą zwracane, dopóki bufor się nie wyczerpie, a potem - zero value i oznaczenie false. Kanał można bezpiecznie czytać do końca po zamknięciu.
ch := make(chan int, 1) ch <- 42 close(ch) v, ok := <-ch // v = 42, ok = true v, ok = <-ch // v = 0, ok = false
3. Kto powinien zamknąć kanał: czytelnik czy pisarz?
Odpowiedź: Zawsze "pisarz" (ten, kto wysyła wartości i wie, kiedy praca jest zakończona). "Czytelnik" nie powinien zamykać kanału, aby nie doprowadzić do warunków wyścigu.
W aplikacji kilka gorutyn pisze do jednego kanału, jedna czyta. Ktoś zamknął kanał w czytniku, a inna gorutyna próbowała jeszcze wysłać - panic.
Zalety: Warunkowo prosta logika interakcji. Wady: Niemożność przewidzenia momentu zamknięcia, paniki, warunki wyścigu.
Pisarz śledzi wszystkie gorutyny, używa sync.WaitGroup, zamyka kanał dopiero po zakończeniu wszystkich pisarzy. Czytelnik poprawnie kończy pętlę range po zamknięciu kanału, bezpiecznie przetwarza błędy.
Zalety: Poprawna synchronizacja, brak wycieków i deadlock, przewidywalne zachowanie. Wady: Nieco bardziej skomplikowana logika, konieczność jawnego zakończenia wszystkich prac.