Canali bufferizzati vs non bufferizzati
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 61 di 107.
I canali che abbiamo usato finora sono non bufferizzati. Non hanno capacità per contenere valori. Un'operazione di invio rimane bloccata finché un'altra goroutine non riceve, e viceversa. I canali bufferizzati aggiungono uno spazio di archiviazione interno, consentendo agli invii di completarsi senza un ricevitore immediato.
Crea un canale bufferizzato passando una capacità a make:
// Non bufferizzato - capacità 0
unbuffered := make(chan int)
// Bufferizzato - capacità 3
buffered := make(chan int, 3)Con un canale bufferizzato, gli invii si bloccano solo quando il buffer è pieno e le ricezioni si bloccano solo quando il buffer è vuoto:
ch := make(chan string, 2)
ch <- "first" // non si blocca - il buffer ha spazio
ch <- "second" // non si blocca - il buffer ha spazio
// ch <- "third" si bloccherebbe - il buffer è pieno
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Usa len(ch) per controllare quanti elementi sono attualmente nel buffer e cap(ch) per la capacità totale.
Quando usare ciascun tipo: I canali non bufferizzati garantiscono una sincronizzazione stretta: il mittente sa che il destinatario ha ricevuto il valore. I canali bufferizzati disaccoppiano i tempi di mittente e destinatario, risultando utili quando produttori e consumatori lavorano a velocità diverse. Tuttavia, i canali bufferizzati possono mascherare bug di sincronizzazione, quindi preferisci quelli non bufferizzati, a meno che tu non abbia un motivo specifico per usare il buffering.
Sfida
FacileCostruiamo un elaboratore batch che dimostri la differenza tra canali bufferizzati e non bufferizzati. Creerai un sistema in cui un produttore invia elementi a un elaboratore, usando canali bufferizzati per separarne i tempi e permettere al produttore di procedere in anticipo.
Organizzerai il codice in due file:
processor.go: definisci la logica di elaborazione batch usando i canali.Crea una struct
Itemcon i campiID(int) eValue(string).Implementa due funzioni:
Producer(items []Item, out chan Item)- Invia ogni elemento al canale di output. Dopo aver inviato ogni elemento, stampa:Produced item [ID]. Dopo aver inviato tutti gli elementi, stampa il numero attuale di elementi in attesa nel buffer usandolen(out)nel formato:Buffer has [count] items. Poi chiudi il canale.Consumer(in chan Item) []string- Riceve gli elementi dal canale di input e li raccoglie in una slice di stringhe formattate. Per ogni elemento ricevuto, la stringa deve essere:Consumed: [ID] - [Value]. Restituisci la slice quando il canale viene chiuso.
main.go: configura il canale bufferizzato e coordina il produttore e il consumatore.Leggi la capacità del buffer, poi il numero di elementi, seguiti dall'ID e dal valore di ciascun elemento. Crea un canale bufferizzato con la capacità specificata. Esegui prima Producer (non come goroutine) per riempire il buffer, poi esegui Consumer per elaborare tutti gli elementi. Stampa ogni risultato consumato su una riga separata.
Verranno forniti i seguenti input:
- Riga 1: capacità del buffer (intero)
- Riga 2: numero di elementi (intero)
- Righe successive: per ogni elemento, due righe: l'ID dell'elemento (intero), poi il suo valore (stringa)
Per esempio, dato:
3
3
1
apple
2
banana
3
cherryIl tuo output dovrebbe essere:
Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherryNota che con una capacità del buffer pari a 3, il produttore può inviare tutti e 3 gli elementi senza bloccarsi e il buffer mostra 3 elementi in attesa prima che il consumatore inizi. Se il buffer fosse più piccolo del numero di elementi, il produttore si bloccherebbe in attesa di spazio.
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi la capacità del buffer
line, _ := reader.ReadString('\n')
bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))
// Leggi il numero di elementi
line, _ = reader.ReadString('\n')
numItems, _ := strconv.Atoi(strings.TrimSpace(line))
// Leggi l'ID e il valore di ciascun elemento
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: Crea un canale con buffer della capacità specificata
// TODO: Esegui Producer (non come goroutine) per riempire il buffer
// TODO: Esegui Consumer per elaborare tutti gli elementi
// TODO: Stampa ogni risultato consumato 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