Kanały buforowane
Pojemność, zamykanie kanałów i range na kanale
Kanały buforowane to bezpłatna lekcja Go Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Go Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Go Academy zawiera 4 lekcji w sumie.
Czym jest kanał buforowany?
Kanał buforowany ma określoną pojemność — wysyłanie nie blokuje się, dopóki w buforze jest miejsce. Kanał działa jak kolejka:
package main
import "fmt"
func main() {
ch := make(chan int, 3) // capacity 3
ch <- 1 // does not block — buffer has room
ch <- 2 // does not block
ch <- 3 // does not block
// ch <- 4 // would block — buffer full
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}Kanał buforowany jako kolejka
Kanały buforowane implementują kolejkę FIFO między gorutynami:
package main
import ("fmt"; "sync")
func producer(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= 5; i++ {
ch <- i
fmt.Printf("produced %d\n", i)
}
}
func main() {
ch := make(chan int, 2) // buffer 2 — producer runs ahead
var wg sync.WaitGroup
wg.Add(1)
go producer(ch, &wg)
wg.Wait()
close(ch)
for v := range ch { fmt.Printf("consumed %d\n", v) }
}len() i cap() dla kanałów
Sprawdzanie bieżącego rozmiaru i pojemności buforowanego kanału:
package main
import "fmt"
func main() {
ch := make(chan string, 5)
ch <- "a"
ch <- "b"
ch <- "c"
fmt.Println("cap:", cap(ch)) // 5
fmt.Println("len:", len(ch)) // 3 — items currently buffered
<-ch
fmt.Println("len:", len(ch)) // 2
}Rozdzielanie producenta i konsumenta
Bufor rozdziela szybkość producenta od szybkości konsumenta. Producent może wyprzedzić konsumenta o rozmiar bufora, zanim się zablokuje:
package main
import ("fmt"; "sync"; "time")
func produce(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
ch <- i
}
}
func consume(ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for v := range ch {
time.Sleep(10 * time.Millisecond) // slower consumer
fmt.Println("consumed:", v)
}
}
func main() {
ch := make(chan int, 3)
var wg sync.WaitGroup
wg.Add(2)
go produce(ch, &wg)
go consume(ch, &wg) // BUG: produce closes before consume sees all
// In practice, coordinate close carefully
fmt.Println("decoupled")
}Semafor z użyciem buforowanego kanału
Buforowany kanał o pojemności N może ograniczać liczbę współbieżnych gorutyn do N — jest to prosty semafor:
package main
import ("fmt"; "sync"; "time")
func main() {
sem := make(chan struct{}, 3) // max 3 concurrent
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
sem <- struct{}{} // acquire
defer func() { <-sem }() // release
fmt.Printf("job %d running\n", i)
time.Sleep(50 * time.Millisecond)
}()
}
wg.Wait()
}Zamykanie buforowanego kanału
Zamknięcie buforowanego kanału pozwala odbiorcom odczytać pozostałe wartości z bufora przed wykryciem zamknięcia:
package main
import "fmt"
func main() {
ch := make(chan int, 5)
for i := 1; i <= 3; i++ { ch <- i }
close(ch)
// Drain all buffered values after close:
for v := range ch {
fmt.Println(v) // 1, 2, 3
}
// range exits after channel closed and drained
}Buforowany a niebuforowany: kiedy wybrać
Wskazówki dotyczące wyboru:
- Niebuforowany: gwarantowane synchroniczne przekazanie, ścisła kolejność, synchronizacja typu rendezvous
- Buforowany: rozdzielenie szybkości producenta i konsumenta, ograniczenie blokowania, implementacja semaforów, zebranie N wyników
- Domyślnie należy wybierać kanały niebuforowane — bufor należy dodawać tylko wtedy, gdy można uzasadnić jego rozmiar
- Zbyt mały bufor → nadal powoduje blokowanie; zbyt duży → ukrywa błędy
Zbieranie wyników za pomocą buforowanego kanału
Buforowanego kanału należy używać do zbierania wyników z równoległych gorutyn bez blokowania:
package main
import ("fmt"; "sync")
func square(n int) int { return n * n }
func main() {
nums := []int{1, 2, 3, 4, 5}
results := make(chan int, len(nums)) // buffer = goroutine count
var wg sync.WaitGroup
for _, n := range nums {
wg.Add(1)
n := n
go func() {
defer wg.Done()
results <- square(n)
}()
}
wg.Wait()
close(results)
for r := range results { fmt.Println(r) }
}Wyciek gorutyny przy pełnym buforze
Jeśli bufor jest pełny i nie ma odbiorcy, nadawca blokuje się na zawsze — powoduje to wyciek gorutyny:
package main
import ("fmt"; "time"; "runtime")
func main() {
ch := make(chan int, 2)
// Start goroutine that fills buffer and then blocks
go func() {
ch <- 1
ch <- 2
ch <- 3 // blocks: buffer full, no receiver
fmt.Println("never reached")
}()
time.Sleep(50 * time.Millisecond)
fmt.Println("goroutines:", runtime.NumGoroutine()) // 2 — leaked
}time.After jako kanał buforowany
time.After zwraca wartość <-chan time.Time z buforem o pojemności 1. Środowisko uruchomieniowe wysyła do niego wartość po upływie określonego czasu, a następnie kanał jest usuwany przez garbage collector:
package main
import ("fmt"; "time")
func doWithTimeout(work func() string) (string, bool) {
result := make(chan string, 1)
go func() { result <- work() }()
select {
case r := <-result:
return r, true
case <-time.After(100 * time.Millisecond):
return "", false
}
}
func main() {
r, ok := doWithTimeout(func() string {
time.Sleep(50 * time.Millisecond)
return "done"
})
fmt.Println(r, ok)
}Szybki test
Co się dzieje, gdy nastąpi próba wysłania do buforowanego kanału, który osiągnął pełną pojemność?
Podsumowanie: kanały buforowane
Podsumowanie:
make(chan T, N)tworzy buforowany kanał o pojemności N- Wysyłanie blokuje się tylko wtedy, gdy bufor jest pełny; odbiór blokuje się tylko wtedy, gdy bufor jest pusty
len(ch)— bieżąca liczba elementów;cap(ch)— pojemność- Należy używać ich do rozdzielania producentów i konsumentów, zbierania wyników równoległych operacji oraz implementowania semaforów
- Zamknięcie pozwala opróżnić bufor przed EOF;
range chobsługuje to automatycznie
Często zadawane pytania
Czy lekcja „Kanały buforowane” jest bezpłatna?
Tak — pełny tekst „Kanały buforowane” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Go Academy, przejdź na CoddyKit PRO. Kurs Go Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Kanały buforowane”?
Pojemność, zamykanie kanałów i range na kanale Ćwiczysz Go Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Go Academy?
Nie wymagamy żadnego doświadczenia. Go Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Kanały buforowane”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Go Academy?
Tak. Każda lekcja Go Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.