0Pricing
Go Academy · Lekcja

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 ch obsł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.

Wszystkie lekcje w tym kursie

  1. Uruchamianie goroutines
  2. Kanały niebuforowane
  3. Kanały buforowane
  4. Kierunek kanałów i wzorce potoków
← Powrót do Go Academy