Canali e comunicazione
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 60 di 107.
I canali sono il meccanismo principale di Go per la comunicazione tra goroutine. Invece di condividere direttamente la memoria, le goroutine inviano e ricevono valori attraverso i canali, seguendo la filosofia di Go: «Non comunicare condividendo la memoria; condividi la memoria comunicando.»
Crea un canale usando la funzione make con la parola chiave chan:
messages := make(chan string)Usa l’operatore freccia <- per inviare e ricevere valori. La freccia indica la direzione del flusso di dati:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // invia al canale
}()
msg := <-messages // ricevi dal canale
fmt.Println(msg) // Uscita: hello
}Per impostazione predefinita, gli invii e le ricezioni bloccano finché l’altra parte non è pronta. Quando la goroutine invia "hello", aspetta finché main non è pronta a ricevere. Questo comportamento bloccante fornisce una sincronizzazione integrata: non serve time.Sleep.
Puoi chiudere un canale per segnalare che non verranno inviati altri valori. I destinatari possono rilevarlo:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}I canali sono tipizzati: un chan string può trasportare solo stringhe. Questa sicurezza dei tipi garantisce che le goroutine comunichino con i tipi di dati corretti, rilevando gli errori in fase di compilazione anziché durante l'esecuzione.
Sfida
FacileCreiamo un sistema di inoltro dei messaggi che mostra come le goroutine comunicano attraverso i canali. Creerai una pipeline in cui i messaggi fluiscono tra diverse fasi di elaborazione, ciascuna eseguita contemporaneamente.
Organizzerai il codice in due file:
relay.go: definisci la pipeline di elaborazione dei messaggi.Crea una struct
Messagecon i campiID(int) eContent(string).Implementa tre funzioni che rappresentano le diverse fasi della pipeline:
Producer(messages []Message, out chan Message)- Riceve una slice di messaggi e invia ciascuno al canale di output. Dopo aver inviato tutti i messaggi, chiudi il canale per segnalare il completamento.Transformer(in chan Message, out chan Message)- Riceve i messaggi dal canale di input, trasforma in maiuscolo il contenuto di ciascun messaggio e invia il messaggio trasformato al canale di output. Quando il canale di input viene chiuso (rilevato usando la forma di ricezione a due valori), chiudi il canale di output.Consumer(in chan Message) []string- Riceve tutti i messaggi dal canale di input e li raccoglie in una slice di stringhe formattate. Ogni stringa deve seguire il formato:Message [ID]: [Content]. Restituisci la slice quando il canale viene chiuso.
main.go: configura la pipeline e coordina il flusso concorrente dei messaggi.Leggi il numero di messaggi, quindi leggi l'ID e il contenuto di ciascun messaggio. Crea i messaggi e configura due canali per collegare le tre fasi della pipeline. Avvia Producer e Transformer come goroutine, quindi esegui Consumer nella goroutine principale per raccogliere i risultati. Stampa ogni risultato su una riga separata.
Verranno forniti i seguenti input:
- Riga 1: numero di messaggi (intero)
- Righe successive: per ogni messaggio, due righe: l'ID del messaggio (intero), poi il suo contenuto (stringa)
Per esempio, dato:
2
1
hello world
2
go channelsL'output dovrebbe essere:
Message 1: HELLO WORLD
Message 2: GO CHANNELSLa pipeline funziona così: Producer invia i messaggi → Transformer li converte in maiuscolo → Consumer raccoglie e formatta i risultati. Ogni freccia rappresenta un canale che collega fasi concorrenti. Usa il pacchetto strings per la trasformazione in maiuscolo.
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi il numero di messaggi
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Leggi i messaggi
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: Crea due canali per collegare le fasi della pipeline
// TODO: Avvia Producer come goroutine
// TODO: Avvia Transformer come goroutine
// TODO: Esegui Consumer nella goroutine main e raccogli i risultati
// TODO: Stampa ogni risultato su una riga separata
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online