Kanały i komunikacja
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 60 z 107.
Kanały to podstawowy mechanizm komunikacji między goroutines w Go. Zamiast bezpośrednio współdzielić pamięć, goroutines wysyłają i odbierają wartości za pośrednictwem kanałów, zgodnie z filozofią Go: „Nie komunikuj się przez współdzielenie pamięci; współdziel pamięć, komunikując się”.
Utwórz kanał za pomocą funkcji make i słowa kluczowego chan:
messages := make(chan string)Użyj operatora strzałki <-, aby wysyłać i odbierać wartości. Strzałka wskazuje kierunek przepływu danych:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // wyślij do kanału
}()
msg := <-messages // odbierz z kanału
fmt.Println(msg) // Wynik: hello
}Domyślnie wysyłanie i odbieranie blokuje, dopóki druga strona nie będzie gotowa. Gdy gorutyna wysyła "hello", czeka, aż main będzie gotowe do odbioru. To blokujące zachowanie zapewnia wbudowaną synchronizację: time.Sleep nie jest potrzebne.
Możesz zamknąć kanał, aby zasygnalizować, że nie zostaną wysłane już żadne wartości. Odbiorcy mogą to wykryć:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}Kanały mają określony typ: chan string może przenosić tylko ciągi znaków. Bezpieczeństwo typów gwarantuje, że gorutyny komunikują się przy użyciu właściwych typów danych, wykrywając błędy na etapie kompilacji, a nie w czasie wykonywania.
Wyzwanie
ŁatwyZbudujmy system przekazywania wiadomości, który pokazuje, jak goroutines komunikują się za pośrednictwem kanałów. Utworzysz potok, w którym wiadomości przepływają między różnymi etapami przetwarzania, a każdy etap działa współbieżnie.
Rozmieścisz swój kod w dwóch plikach:
relay.go: Zdefiniuj potok przetwarzania wiadomości.Utwórz strukturę
Messagez polamiID(int) iContent(string).Zaimplementuj trzy funkcje reprezentujące różne etapy potoku:
Producer(messages []Message, out chan Message)- Przyjmuje wycinek wiadomości i wysyła każdą z nich do kanału wyjściowego. Po wysłaniu wszystkich wiadomości zamknij kanał, aby zasygnalizować zakończenie.Transformer(in chan Message, out chan Message)- Odbiera wiadomości z kanału wejściowego, zamienia treść każdej wiadomości na wielkie litery i wysyła przekształconą wiadomość do kanału wyjściowego. Gdy kanał wejściowy zostanie zamknięty (co wykrywa się za pomocą formy odbioru z dwoma wartościami), zamknij kanał wyjściowy.Consumer(in chan Message) []string- Odbiera wszystkie wiadomości z kanału wejściowego i zbiera je w wycinku sformatowanych ciągów znaków. Każdy ciąg znaków powinien mieć format:Message [ID]: [Content]. Zwróć wycinek po zamknięciu kanału.
main.go: Skonfiguruj potok i zarządzaj współbieżnym przepływem wiadomości.Odczytaj liczbę wiadomości, a następnie identyfikator i treść każdej wiadomości. Utwórz wiadomości i skonfiguruj dwa kanały łączące trzy etapy potoku. Uruchom Producer i Transformer jako goroutines, a następnie uruchom Consumer w głównej goroutine, aby zebrać wyniki. Wypisz każdy wynik w osobnym wierszu.
Podane zostaną następujące dane wejściowe:
- Wiersz 1: Liczba wiadomości (liczba całkowita)
- Kolejne wiersze: Dla każdej wiadomości dwa wiersze — identyfikator wiadomości (liczba całkowita), a następnie jej treść (ciąg znaków)
Na przykład, dla danych:
2
1
hello world
2
go channelsWynik powinien być następujący:
Message 1: HELLO WORLD
Message 2: GO CHANNELSPotok działa tak: Producer wysyła wiadomości → Transformer zamienia je na wielkie litery → Consumer zbiera i formatuje wyniki. Każda strzałka reprezentuje kanał łączący współbieżne etapy. Do zamiany tekstu na wielkie litery użyj pakietu strings.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Odczytaj liczbę wiadomości
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Odczytaj wiadomości
messages := make([]Message, n)
for i := 0; i < n; i++ {
idLine, _ := reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(idLine))
contentLine, _ := reader.ReadString('\n')
content := strings.TrimSpace(contentLine)
messages[i] = Message{ID: id, Content: content}
}
// TODO: Utwórz dwa kanały łączące etapy potoku
// TODO: Uruchom Producer jako gorutynę
// TODO: Uruchom Transformer jako gorutynę
// TODO: Uruchom Consumer w głównej gorutynie i zbierz wyniki
// TODO: Wypisz każdy wynik 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