Kanały buforowane i niebuforowane
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 61 z 107.
Kanały, których używaliśmy do tej pory, są niebuforowane. Nie mają pojemności pozwalającej przechowywać wartości. Operacja wysyłania blokuje się, dopóki inna gorutyna nie odbierze wartości, i odwrotnie. Kanały buforowane dodają wewnętrzną pamięć, dzięki czemu wysyłanie może zakończyć się bez natychmiastowego odbiorcy.
Utwórz buforowany kanał, przekazując pojemność do make:
// Niebuforowany - pojemność 0
unbuffered := make(chan int)
// Buforowany - pojemność 3
buffered := make(chan int, 3)W przypadku kanału buforowanego wysyłanie blokuje się tylko wtedy, gdy bufor jest pełny, a odbieranie — tylko wtedy, gdy bufor jest pusty:
ch := make(chan string, 2)
ch <- "first" // nie blokuje - w buforze jest miejsce
ch <- "second" // nie blokuje - w buforze jest miejsce
// ch <- "third" zablokowałoby się - bufor jest pełny
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Użyj len(ch), aby sprawdzić, ile elementów znajduje się obecnie w buforze, a cap(ch), aby sprawdzić jego całkowitą pojemność.
Kiedy używać poszczególnych rodzajów: Kanały niebuforowane zapewniają ścisłą synchronizację: nadawca wie, że odbiorca otrzymał wartość. Kanały buforowane uniezależniają czas działania nadawcy i odbiorcy, co jest przydatne, gdy producenci i konsumenci pracują z różną szybkością. Kanały buforowane mogą jednak maskować błędy synchronizacji, dlatego preferuj kanały niebuforowane, chyba że masz konkretny powód, by użyć buforowania.
Wyzwanie
ŁatwyZbudujemy procesor wsadowy, który pokazuje różnicę między kanałami buforowanymi i niebuforowanymi. Utworzysz system, w którym producent wysyła elementy do procesora, używając kanałów buforowanych, aby oddzielić ich tempo pracy i umożliwić producentowi pracę z wyprzedzeniem.
Rozmieścisz kod w dwóch plikach:
processor.go: Zdefiniuj logikę przetwarzania wsadowego za pomocą kanałów.Utwórz strukturę
Itemz polamiID(int) iValue(string).Zaimplementuj dwie funkcje:
Producer(items []Item, out chan Item)- Wysyła każdy element do kanału wyjściowego. Po wysłaniu każdego elementu wypisz:Produced item [ID]. Po wysłaniu wszystkich elementów wypisz bieżącą liczbę elementów oczekujących w buforze, używająclen(out)w formacie:Buffer has [count] items. Następnie zamknij kanał.Consumer(in chan Item) []string- Odbiera elementy z kanału wejściowego i zbiera je w tablicy sformatowanych ciągów znaków. Dla każdego odebranego elementu ciąg powinien mieć postać:Consumed: [ID] - [Value]. Zwróć tablicę po zamknięciu kanału.
main.go: Skonfiguruj kanał buforowany oraz skoordynuj działanie producenta i konsumenta.Odczytaj pojemność bufora, następnie liczbę elementów, a potem identyfikator i wartość każdego elementu. Utwórz kanał buforowany o określonej pojemności. Najpierw uruchom Producer (nie jako gorutynę), aby wypełnić bufor, a następnie uruchom Consumer, aby przetworzyć wszystkie elementy. Wypisz każdy przetworzony wynik w osobnym wierszu.
Dane wejściowe będą następujące:
- Wiersz 1: Pojemność bufora (liczba całkowita)
- Wiersz 2: Liczba elementów (liczba całkowita)
- Kolejne wiersze: Dla każdego elementu dwa wiersze — identyfikator elementu (liczba całkowita), a następnie jego wartość (ciąg znaków)
Na przykład, dla danych:
3
3
1
apple
2
banana
3
cherryOczekiwany wynik:
Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherryZauważ, że przy pojemności bufora równej 3 producent może wysłać wszystkie 3 elementy bez blokowania, a przed rozpoczęciem pracy konsumenta w buforze widać 3 oczekujące elementy. Jeśli bufor byłby mniejszy niż liczba elementów, producent zablokowałby się, czekając na wolne miejsce.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Odczytaj pojemność bufora
line, _ := reader.ReadString('\n')
bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))
// Odczytaj liczbę elementów
line, _ = reader.ReadString('\n')
numItems, _ := strconv.Atoi(strings.TrimSpace(line))
// Odczytaj ID i wartość każdego elementu
items := make([]Item, numItems)
for i := 0; i < numItems; i++ {
line, _ = reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(line))
line, _ = reader.ReadString('\n')
value := strings.TrimSpace(line)
items[i] = Item{ID: id, Value: value}
}
// TODO: Utwórz kanał buforowany o określonej pojemności
// TODO: Uruchom Producer (nie jako goroutine), aby wypełnić bufor
// TODO: Uruchom Consumer, aby przetworzyć wszystkie elementy
// TODO: Wypisz każdy wynik pobrany z kanału w osobnym wierszu
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online