Grazie per aver sottoscritto

Introduzione

Quando ho iniziato a lavorare con i canali di Go per la prima volta, ho commesso l’errore di pensare ai canali come a una struttura dati. Ho visto i canali come una coda che forniva l’accesso sincronizzato automatico tra le goroutine. Questa comprensione strutturale mi ha causato la scrittura di un sacco di codice concorrente cattivo e complicato.

Ho imparato nel tempo che è meglio dimenticare come i canali sono strutturati e concentrarsi su come si comportano. Così ora quando si tratta di canali, penso a una cosa: segnalazione. Un canale consente a una goroutine di segnalare un’altra goroutine su un particolare evento. La segnalazione è al centro di tutto ciò che dovresti fare con i canali. Pensare ai canali come un meccanismo di segnalazione ti consentirà di scrivere codice migliore con un comportamento ben definito e più preciso.

Per capire come funziona la segnalazione, dobbiamo capire i suoi tre attributi:

  • Garanzia di consegna
  • Stato
  • Con o senza dati

Questi tre attributi lavorano insieme per creare una filosofia di design intorno segnalazione. Dopo aver discusso questi attributi, fornirò una serie di esempi di codice che dimostrano la segnalazione con questi attributi applicati.

Garanzia di consegna

La garanzia di consegna si basa su una domanda: “Ho bisogno di una garanzia che il segnale inviato da una particolare goroutine sia stato ricevuto?”

In altre parole, dato questo esempio nel listato 1:

Listato 1

01 go func() {02 p := <-ch // Receive03 }()0405 ch <- "paper" // Send

La goroutine di invio ha bisogno di una garanzia che il paper inviato sul canale sulla linea 05 è stato ricevuto dalla goroutine sulla linea 02 prima di passare?

In base alla risposta a questa domanda, saprai quale dei due tipi di canali utilizzare: Unbuffered o Buffered. Ogni canale fornisce un comportamento diverso intorno garanzie di consegna.

Figura 1 : Garanzia di consegna

 Garanzia di consegna

Le garanzie sono importanti e, se non la pensi così, ho un sacco di cose che voglio venderti. Certo, sto cercando di fare una battuta, ma non ti innervosisci quando non hai garanzie nella vita? Avere una forte comprensione della necessità o meno di una garanzia è fondamentale quando si scrive software simultaneo. Mentre continuiamo, imparerai come decidere.

Stato

Il comportamento di un canale è direttamente influenzato dal suo stato corrente. Lo stato di un canale può essere nullo, aperto o chiuso.

Listato 2 sotto mostra come dichiarare o inserire un canale in ciascuno di questi tre stati.

Elenco 2

// ** nil channel// A channel is in a nil state when it is declared to its zero valuevar ch chan string// A channel can be placed in a nil state by explicitly setting it to nil.ch = nil// ** open channel// A channel is in a open state when it's made using the built-in function make.ch := make(chan string) // ** closed channel// A channel is in a closed state when it's closed using the built-in function close.close(ch)

Lo stato determina il comportamento delle operazioni di invio e ricezione.

I segnali vengono inviati e ricevuti attraverso un canale. Non dire lettura/scrittura perché i canali non eseguono I/O.

Figura 2: Stato

Stato

Quando un canale è in uno stato zero, qualsiasi tentativo di invio o ricezione sul canale verrà bloccato. Quando un canale è in uno stato aperto, i segnali possono essere inviati e ricevuti. Quando un canale viene inserito in uno stato chiuso, i segnali non possono più essere inviati ma è ancora possibile ricevere segnali.

Questi stati forniranno i diversi comportamenti necessari per le diverse situazioni che si incontrano. Quando si combina lo stato con la Garanzia di consegna, è possibile iniziare ad analizzare i costi/benefici che si stanno incorrendo a seguito delle scelte di progettazione. In molti casi, sarai anche in grado di individuare rapidamente i bug semplicemente leggendo il codice, perché capisci come si comporterà il canale.

Con e senza dati

L’ultimo attributo di segnalazione che deve essere preso in considerazione è se è necessario segnalare con o senza dati.

Si segnala con i dati eseguendo un invio su un canale.

Elenco 3

01 ch <- "paper"

Quando segnali con i dati, di solito è perché:

  • A goroutine viene chiesto di iniziare una nuova attività.
  • Una goroutine riporta un risultato.

Si segnala senza dati chiudendo un canale.

Elenco 4

01 close(ch)

Quando segnali senza dati, di solito è perché:

  • A un goroutine viene detto di fermare ciò che stanno facendo.
  • Un goroutine riporta che sono fatti senza alcun risultato.
  • Un goroutine segnala che ha completato l’elaborazione e si è spento.

Ci sono eccezioni a queste regole, ma questi sono i principali casi d’uso e quelli su cui ci concentreremo in questo post. Considererei le eccezioni a queste regole come un odore di codice iniziale.

Un vantaggio della segnalazione senza dati è che una singola goroutine può segnalare molte goroutine contemporaneamente. La segnalazione con i dati è sempre uno scambio 1 a 1 tra goroutine.

Segnalazione con dati

Quando si sta per segnalare con i dati, ci sono tre opzioni di configurazione del canale è possibile scegliere a seconda del tipo di garanzia è necessario.

Figura 3 : Segnalazione con dati

Le tre opzioni di canale sono Unbuffered, Buffered >1 o Buffered =1.

  • Garanzia

    • Un canale senza buffer garantisce che un segnale inviato è stato ricevuto.
      • Perché la ricezione del segnale avviene prima del completamento dell’invio del segnale.
  • Nessuna garanzia

    • Un canale bufferizzato di dimensioni > 1 non garantisce che un segnale inviato sia stato ricevuto.
      • Perché l’invio del segnale avviene prima del completamento della ricezione del segnale.
  • Garanzia ritardata

    • Un canale tamponato di dimensione =1 offre una garanzia ritardata. Può garantire che il segnale precedente inviato sia stato ricevuto.
      • Perché la ricezione del primo segnale, accade prima che l’invio del secondo segnale completa.

La dimensione del buffer non deve mai essere un numero casuale, deve sempre essere calcolata per qualche vincolo ben definito. Non c’è infinito nell’informatica, tutto deve avere qualche vincolo ben definito, sia che si tratti di tempo o spazio.

Segnalazione senza dati

La segnalazione senza dati è riservata principalmente alla cancellazione. Permette a una goroutine di segnalare un’altra goroutine per annullare ciò che stanno facendo e andare avanti. La cancellazione può essere implementata utilizzando entrambi i canali Unbuffered e Buffered, ma l’utilizzo di un canale Buffered quando non verranno inviati dati è un odore di codice.

Figura 4 : Segnalazione senza dati

La funzione incorporata close viene utilizzata per segnalare senza dati. Come spiegato sopra nella sezione Stato, è ancora possibile ricevere segnali su un canale che è chiuso. In effetti, qualsiasi ricezione su un canale chiuso non si bloccherà e l’operazione di ricezione ritorna sempre.

Nella maggior parte dei casi si desidera utilizzare il pacchetto libreria standard context per implementare la segnalazione senza dati. Il pacchetto context utilizza un canale Unbuffered sotto per la segnalazione e la funzione incorporata close per segnalare senza dati.

Se si sceglie di utilizzare il proprio canale per la cancellazione, piuttosto che il pacchetto contestuale, il canale dovrebbe essere di tipo chan struct{}. È lo spazio zero, modo idiomatico per indicare un canale utilizzato solo per la segnalazione.

Scenari

Con questi attributi in atto, il modo migliore per capire ulteriormente come funzionano in pratica è quello di eseguire attraverso una serie di scenari di codice. Mi piace pensare a goroutine come persone quando sto leggendo e scrivendo codice basato sul canale. Questa visualizzazione aiuta davvero e la userò come aiuto di seguito.

Segnale con canali Data – Guarantee – Unbuffered

Quando è necessario sapere che un segnale inviato è stato ricevuto, entrano in gioco due scenari. Questi sono Attendere l’attività e attendere il risultato.

Scenario 1-Attendi l’attività

Pensa di essere un manager e assumere un nuovo dipendente. In questo scenario, si desidera che il nuovo dipendente esegua un’attività, ma è necessario attendere fino a quando non si è pronti. Questo perché è necessario consegnare loro un pezzo di carta prima di iniziare.

Elenco 5
https://play.golang.org/p/BnVEHRCcdh

01 func waitForTask() {02 ch := make(chan string)0304 go func() {05 p := <-ch0607 // Employee performs work here.0809 // Employee is done and free to go.10 }()1112 time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)1314 ch <- "paper"15 }

Sulla riga 02 nel listato 5, viene creato un canale non buffer con l’attributo che i dati string verranno inviati con il segnale. Poi sulla linea 04, un dipendente viene assunto e ha detto di aspettare il segnale sulla linea 05 prima di fare il loro lavoro. La riga 05 è il canale di ricezione, causando il blocco del dipendente durante l’attesa del pezzo di carta che verrà inviato. Una volta che la carta viene ricevuta dal dipendente, il dipendente esegue il lavoro e poi è fatto e libero di andare.

Tu come manager stai lavorando contemporaneamente al tuo nuovo dipendente. Quindi, dopo aver assunto il dipendente sulla linea 04, ti ritrovi (sulla linea 12) a fare ciò che devi fare per sbloccare e segnalare il dipendente. Nota, non si sapeva quanto tempo ci sarebbe voluto per preparare questo pezzo di carta è necessario inviare.

Alla fine si è pronti a segnalare il dipendente. Sulla linea 14, si esegue un segnale con i dati, i dati sono quel pezzo di carta. Poiché viene utilizzato un canale Unbuffered, si ottiene una garanzia che il dipendente ha ricevuto la carta una volta completata l’operazione di invio. La ricezione avviene prima dell’invio.

Tecnicamente tutto quello che sai è che il dipendente ha la carta al termine dell’operazione di invio del canale. Dopo entrambe le operazioni del canale, lo scheduler può scegliere di eseguire qualsiasi istruzione desiderata. La riga successiva di codice eseguita dall’utente o dal dipendente non è deterministica. Ciò significa che l’utilizzo di istruzioni di stampa può ingannare l’ordine delle cose.

Scenario 2-Attendere il risultato

In questo prossimo scenario le cose vengono invertite. Questa volta vuoi che il tuo nuovo dipendente esegua un’attività immediatamente quando vengono assunti e devi attendere il risultato del loro lavoro. Devi aspettare perché hai bisogno della carta da loro prima di poter continuare.

Elenco 6
https://play.golang.org/p/VFAWHxIQTP

01 func waitForResult() {02 ch := make(chan string)0304 go func() {05 time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)0607 ch <- "paper"0809 // Employee is done and free to go.10 }()1112 p := <-ch13 }

Sulla riga 02 nel listato 6, viene creato un canale non buffer con l’attributo che i dati string verranno inviati con il segnale. Poi sulla linea 04, un dipendente viene assunto e viene immediatamente messo al lavoro. Dopo aver assunto il dipendente sulla linea 04, ti ritrovi accanto alla linea 12 in attesa del rapporto cartaceo.

Una volta che il lavoro è completato dal dipendente sulla linea 05, ti inviano il risultato sulla linea 07 eseguendo un canale di invio con i dati. Poiché si tratta di un canale senza buffer, la ricezione avviene prima dell’invio e al dipendente viene garantito di aver ricevuto il risultato. Una volta che il dipendente ha questa garanzia, sono fatti e liberi di andare. In questo scenario, non avete idea di quanto tempo sta andando a prendere il dipendente per completare l’attività.

Costi/benefici

Un canale senza buffer garantisce la ricezione di un segnale inviato. Questo è grande, ma nulla è gratuito. Il costo di questa garanzia è latenza sconosciuta. Nello scenario Attendi attività, il dipendente non ha idea di quanto tempo ci vorrà per inviare quel documento. Nello scenario Attendi il risultato, non hai idea di quanto tempo impiegherà il dipendente a inviarti quel risultato.

In entrambi gli scenari, questa latenza sconosciuta è qualcosa con cui dobbiamo convivere perché è richiesta la garanzia. La logica non funziona senza questo comportamento garantito.

Segnale con dati – Nessuna garanzia-Canali tamponati >1

Quando non è necessario sapere che un segnale inviato è stato ricevuto, questi due scenari entrano in gioco: Fan Out e Drop.

Un canale bufferizzato ha uno spazio ben definito che può essere utilizzato per memorizzare i dati inviati. Così come si fa a decidere quanto spazio avete bisogno? Rispondi a queste domande:

  • Ho una quantità ben definita di lavoro da completare?
    • Quanto lavoro c’è?
  • Se il mio dipendente non riesce a tenere il passo, posso scartare qualsiasi nuovo lavoro?
    • Quanto lavoro eccezionale mi mette alla capacità?
  • Quale livello di rischio sono disposto ad accettare se il mio programma termina in modo imprevisto?
    • Qualsiasi cosa in attesa nel buffer andrà persa.

Se queste domande non hanno senso per il comportamento che stai modellando, è un odore di codice che l’utilizzo di un canale bufferizzato più grande di 1 è probabilmente sbagliato.

Scenario 1 – Fan Out

Un modello di fan out consente di lanciare un numero ben definito di dipendenti a un problema che lavorano contemporaneamente. Dal momento che hai un dipendente per ogni attività, sai esattamente quanti rapporti riceverai. Puoi assicurarti che ci sia la giusta quantità di spazio nella tua casella per ricevere tutti quei rapporti. Questo ha il vantaggio dei tuoi dipendenti che non hanno bisogno di aspettare che tu invii il loro rapporto. Tuttavia hanno bisogno di ogni prendere una svolta mettendo il rapporto nella tua casella se arrivano alla casella o vicino allo stesso tempo.

Immagina di essere di nuovo il manager, ma questa volta assumi un team di dipendenti. Si dispone di un compito individuale che si desidera che ogni dipendente per eseguire. Come ogni singolo dipendente finisce il loro compito, hanno bisogno di fornire un rapporto di carta che deve essere collocato nella vostra scatola sulla scrivania.

Elenco 7
https://play.golang.org/p/8HIt2sabs_

01 func fanOut() {02 emps := 2003 ch := make(chan string, emps)0405 for e := 0; e < emps; e++ {06 go func() {07 time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)08 ch <- "paper"09 }()10 }1112 for emps > 0 {13 p := <-ch14 fmt.Println(p)15 emps--16 }17 }

Sulla riga 03 nel listato 7, viene creato un canale bufferizzato con l’attributo che i dati string verranno inviati con il segnale. Questa volta il canale viene creato con 20 buffer grazie alla variabile emps dichiarata sulla riga 02.

Tra le righe da 05 a 10, vengono assunti 20 dipendenti che si mettono immediatamente al lavoro. Non avete idea di quanto tempo ogni dipendente sta per prendere sulla linea 07. Poi sulla linea 08, i dipendenti inviano il rapporto di carta, ma questa volta l’invio non blocca in attesa di una ricezione. Poiché c’è spazio nella casella per ogni dipendente, l’invio sul canale è in competizione solo con altri dipendenti che potrebbero voler inviare il loro rapporto allo stesso tempo o vicino.

Il codice tra le righe da 12 a 16 è tutto ciò che. Questo è dove si attende per tutti i 20 dipendenti a finire il loro lavoro e inviare il loro rapporto. Sulla linea 12, ci si trova in un ciclo e sulla linea 13 si sono bloccati in un canale ricevere in attesa di rapporti. Una volta ricevuto un rapporto, il rapporto viene stampato sulla riga 14 e la variabile contatore locale viene decrementata per indicare che un dipendente è fatto.

Scenario 2-Drop

Un modello di drop consente di buttare via il lavoro quando i dipendenti sono a capacità. Questo ha il vantaggio di continuare ad accettare il lavoro dai tuoi clienti e non applicare mai contropressione o latenza nell’accettazione di quel lavoro. La chiave qui è sapere quando si è veramente a capacità in modo da non sotto o sopra impegnarsi per la quantità di lavoro si tenterà di ottenere fatto. Di solito test di integrazione o metriche è ciò di cui hai bisogno per aiutarti a identificare questo numero.

Immagina di essere di nuovo il manager e di assumere un singolo dipendente per svolgere il lavoro. Si dispone di un’attività individuale che si desidera che il dipendente esegua. Come il dipendente finisce il loro compito non si cura di sapere che sono fatti. Tutto ciò che è importante è se è possibile o non è possibile inserire un nuovo lavoro nella scatola. Se non è possibile eseguire l’invio, allora sai che la tua casella è piena e il dipendente è a capacità. A questo punto il nuovo lavoro deve essere scartato in modo che le cose possano continuare a muoversi.

Elenco 8
https://play.golang.org/p/PhFUN5itiv

01 func selectDrop() {02 const cap = 503 ch := make(chan string, cap)0405 go func() {06 for p := range ch {07 fmt.Println("employee : received :", p)08 }09 }()1011 const work = 2012 for w := 0; w < work; w++ {13 select {14 case ch <- "paper":15 fmt.Println("manager : send ack")16 default:17 fmt.Println("manager : drop")18 }19 }2021 close(ch)22 }

Alla riga 03 nel listato 8, viene creato un canale bufferizzato con l’attributo che i dati string verranno inviati con il segnale. Questa volta il canale viene creato con 5 buffer grazie alla costante cap dichiarata sulla riga 02.

Tra le righe da 05 a 09 viene assunto un singolo dipendente per gestire il lavoro. A for range viene utilizzato per la ricezione del canale. Ogni volta che un pezzo di carta viene ricevuto viene elaborato sulla linea 07.

Tra le righe da 11 a 19 si tenta di inviare 20 pezzi di carta al proprio dipendente. Questa volta viene utilizzata un’istruzione select per eseguire l’invio all’interno del primo case sulla riga 14. Poiché la clausola default viene utilizzata all’interno di select sulla riga 16, se l’invio si blocca perché non c’è più spazio nel buffer, l’invio viene abbandonato eseguendo la riga 17.

Infine sulla linea 21, la funzione incorporata close viene chiamata contro il canale. Questo segnalerà senza dati al dipendente che sono fatti e liberi di andare una volta completato il loro lavoro assegnato..

Costi/benefici

Un canale bufferizzato maggiore di 1 non garantisce mai la ricezione di un segnale inviato. C’è un vantaggio di allontanarsi da questa garanzia, che è la latenza ridotta o assente nella comunicazione tra due goroutine. Nello scenario Fan Out, c’è uno spazio buffer per ogni dipendente che invierà un report. Nello scenario Drop, il buffer viene misurato per la capacità e se viene raggiunta la capacità, il lavoro viene eliminato in modo che le cose possano continuare a muoversi.

In entrambe le opzioni, questa mancanza di garanzia è qualcosa con cui dobbiamo convivere perché la riduzione della latenza è più importante. Il requisito di latenza da zero a minima non rappresenta un problema per la logica generale del sistema.

Segnale con Data-Delayed Guarantee-Buffered Channel 1

Quando è necessario sapere se il segnale precedente inviato è stato ricevuto prima di inviare un nuovo segnale, entra in gioco lo scenario Wait For Tasks.

Scenario 1-Attendi le attività

In questo scenario hai un nuovo dipendente ma faranno più di un compito. Avete intenzione di dar loro da mangiare molti compiti, uno dopo l’altro. Tuttavia, essi devono terminare ogni singola attività prima di poter iniziare una nuova. Dal momento che possono lavorare solo su un’attività alla volta, potrebbero esserci problemi di latenza tra la consegna del lavoro. Se la latenza potrebbe essere ridotta senza perdere la garanzia che il dipendente stia lavorando all’attività successiva, potrebbe essere d’aiuto.

Questo è dove un canale bufferizzato di 1 ha beneficio. Se tutto funziona al ritmo previsto tra te e il dipendente, nessuno di voi dovrà aspettare l’altro. Ogni volta che si invia un pezzo di carta, il buffer è vuoto. Ogni volta che il tuo dipendente raggiunge più lavoro, il buffer è pieno. È una perfetta simmetria del flusso di lavoro.

La parte migliore è questa. Se in qualsiasi momento tenti di inviare un pezzo di carta e non puoi perché il buffer è pieno, sai che il tuo dipendente sta avendo un problema e ti fermi. Questo è dove quella garanzia ritardata entra in gioco. Quando il buffer è vuoto e si esegue l’invio, si ha la garanzia che il dipendente ha preso l’ultimo pezzo di lavoro inviato. Se si esegue l’invio e non è possibile, si ha la garanzia che non hanno.

Elenco 9
https://play.golang.org/p/4pcuKCcAK3

01 func waitForTasks() {02 ch := make(chan string, 1)0304 go func() {05 for p := range ch {06 fmt.Println("employee : working :", p)07 }08 }()0910 const work = 1011 for w := 0; w < work; w++ {12 ch <- "paper"13 }1415 close(ch)16 }

Alla riga 02 nel listato 9, viene creato un canale bufferizzato di dimensione 1 con l’attributo che i dati string verranno inviati con il segnale. Tra le righe da 04 a 08 viene assunto un singolo dipendente per gestire il lavoro. A for range viene utilizzato per la ricezione del canale. Ogni volta che un pezzo di carta viene ricevuto viene elaborato sulla linea 06.

Tra le righe da 10 a 13 inizi a inviare le tue attività al dipendente. Se il dipendente può eseguire il più velocemente possibile inviare, la latenza tra voi due è ridotta. Ma con ogni invio si esegue con successo, si ha la garanzia che l’ultimo pezzo di lavoro che hai inviato viene lavorato su.

Infine sulla linea 15, la funzione incorporata close viene chiamata contro il canale. Questo segnalerà senza dati al dipendente che sono fatti e liberi di andare. Tuttavia, l’ultimo lavoro inviato verrà ricevuto (svuotato) prima che for range sia terminato.

Segnale senza contesto dati

In quest’ultimo scenario vedrai come puoi annullare una goroutine in esecuzione usando un valore Context dal pacchetto context. Tutto questo funziona sfruttando un canale Unbuffered che viene chiuso per eseguire un segnale senza dati.

Sei il manager un’ultima volta e assumi un singolo dipendente per svolgere il lavoro. Questa volta non si è disposti ad aspettare per qualche quantità sconosciuta di tempo per il dipendente per finire. Sei su una scadenza discreta e se il dipendente non finisce in tempo, non sei disposto ad aspettare.

Elenco 10
https://play.golang.org/p/6GQbN5Z7vC

01 func withTimeout() {02 duration := 50 * time.Millisecond0304 ctx, cancel := context.WithTimeout(context.Background(), duration)05 defer cancel()0607 ch := make(chan string, 1)0809 go func() {10 time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)11 ch <- "paper"12 }()1314 select {15 case p := <-ch:16 fmt.Println("work complete", p)1718 case <-ctx.Done():19 fmt.Println("moving on")20 }21 }

Alla riga 02 del listato 10, viene dichiarato un valore di duration che rappresenta il tempo che il dipendente dovrà terminare l’attività. Questo valore viene utilizzato sulla riga 04 per creare un valore context.Context con un timeout di 50 millisecondi. La funzione WithTimeout del pacchetto context restituisce un valore Context e una funzione di cancellazione.

Il pacchetto context crea una goroutine che chiuderà il canale Unbuffered associato al valore Context una volta raggiunta la durata. Sei responsabile di chiamare la funzione cancel indipendentemente da come vanno le cose. Questo pulirà le cose che sono state create per Context. È ok che la funzione cancel venga chiamata più di una volta.

Alla riga 05, la funzione cancel viene posticipata per essere eseguita una volta terminata questa funzione. Sulla riga 07 viene creato un canale bufferizzato di 1, che verrà utilizzato dal dipendente per inviarti il risultato del loro lavoro. Quindi, sulle linee da 09 a 12, il dipendente viene assunto e immediatamente messo al lavoro. Non avete idea di quanto tempo sta andando a prendere il dipendente per finire.

Tra le righe da 14 a 20 si utilizza l’istruzione select per ricevere su due canali. La ricezione sulla linea 15, si attende che il dipendente di inviare il loro risultato. La ricezione sulla riga 18, si aspetta di vedere se il pacchetto context sta per segnalare che i 50 millisecondi sono in su. Qualunque sia il segnale che ricevi per primo sarà quello elaborato.

Un aspetto importante di questo algoritmo è l’uso del canale Buffered di 1. Se il dipendente non finisce in tempo, si sta muovendo su senza dare al dipendente alcun preavviso. Dal punto di vista dei dipendenti, ti invieranno sempre il rapporto sulla linea 11 e sono ciechi se sei lì o non lo ricevi. Se si utilizza un canale Unbuffered, il dipendente bloccherà per sempre cercando di inviare il rapporto se si sposta su. Questo creerebbe una perdita di goroutine. Quindi viene utilizzato un canale bufferizzato di 1 per evitare che ciò accada.

Conclusione

Gli attributi di segnalazione attorno alle garanzie, allo stato del canale e all’invio sono importanti da conoscere e capire quando si utilizzano i canali (o la concorrenza). Ti aiuteranno a guidare l’utente nell’implementazione del miglior comportamento necessario per i programmi e gli algoritmi simultanei che stai scrivendo. Ti aiuteranno a trovare bug e annusare codice potenzialmente cattivo.

In questo post ho condiviso alcuni programmi di esempio che mostrano come gli attributi di segnalazione funzionano in diversi scenari. Ci sono eccezioni ad ogni regola, ma questi modelli sono una buona base per iniziare.

Rivedere questi contorni come una sintesi di quando e come pensare in modo efficace e utilizzare i canali:

Meccanica del linguaggio

  • Usa i canali per orchestrare e coordinare le goroutine.
    • Concentrarsi sugli attributi di segnalazione e non sulla condivisione dei dati.
    • Segnalazione con dati o senza dati.
    • Mettere in discussione il loro uso per sincronizzare l’accesso allo stato condiviso.
      • Ci sono casi in cui i canali possono essere più semplici per questo, ma inizialmente domanda.
  • Canali unbuffered:
    • La ricezione avviene prima dell’invio.
    • Vantaggio: 100% di garanzia il segnale è stato ricevuto.
    • Costo: Latenza sconosciuta quando verrà ricevuto il segnale.
  • Canali bufferizzati:
    • L’invio avviene prima della ricezione.
    • Vantaggio: ridurre la latenza di blocco tra le segnalazioni.
    • Costo: Nessuna garanzia quando il segnale è stato ricevuto.
      • Più grande è il buffer, minore è la garanzia.
      • Buffer di 1 può dare un ritardo di invio di garanzia.
  • Chiusura canali:
    • La chiusura avviene prima della ricezione (come Buffered).
    • Segnalazione senza dati.
    • Perfetto per segnalare cancellazioni e scadenze.
  • nil canali:
    • Inviare e ricevere blocco.
    • Disattivare la segnalazione
    • Perfetto per limitare la velocità o arresti a breve termine.

Filosofia di progettazione

  • Se un dato invio su un canale PUÒ causare il blocco della goroutine di invio:
    • Non è consentito utilizzare un canale bufferizzato maggiore di 1.
      • I buffer più grandi di 1 devono avere ragione / misure.
    • Deve sapere cosa succede quando l’invio di blocchi goroutine.
  • Se un dato invio su un canale NON causerà il blocco della goroutine di invio:
    • Hai il numero esatto di buffer per ogni invio.
      • Modello a ventaglio
    • Hai il buffer misurato per la capacità massima.
      • Modello di goccia
  • Meno è più con i buffer.
    • Non pensare alle prestazioni quando si pensa ai buffer.
    • I buffer possono aiutare a ridurre la latenza di blocco tra le segnalazioni.
      • Ridurre la latenza di blocco verso lo zero non significa necessariamente un throughput migliore.
      • Se un buffer di uno ti dà un throughput abbastanza buono, tienilo.
      • Domanda buffer più grandi di uno e misura per dimensione.
      • Trova il buffer più piccolo possibile che fornisce un throughput abbastanza buono.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.