Kanalen gebruiken als communicatiemechanisme
Een kanaal in Go is een communicatiemechanisme tussen goroutines. De benadering van Go voor gelijktijdigheid is: 'Niet communiceren door geheugen te delen; deel in plaats daarvan geheugen door te communiceren'. Wanneer u een waarde van de ene goroutine naar een andere wilt verzenden, gebruikt u kanalen. Laten we eens kijken hoe ze werken en hoe u ze kunt gaan gebruiken om gelijktijdige Go-programma's te schrijven.
Kanaalsyntaxis
Omdat een kanaal een communicatiemechanisme is dat gegevens verzendt en ontvangt, heeft het ook een type. Dat betekent dat u alleen gegevens kunt verzenden voor het type dat het kanaal ondersteunt. U gebruikt het trefwoord chan
als het gegevenstype voor een kanaal, maar u moet ook het gegevenstype opgeven dat door het kanaal gaat, zoals een int
type.
Telkens wanneer u een kanaal declareert of een kanaal als parameter in een functie wilt opgeven, moet chan <type>
u , zoals chan int
. Als u een kanaal wilt maken, gebruikt u de ingebouwde make()
functie:
ch := make(chan int)
Een kanaal kan twee bewerkingen uitvoeren: gegevens verzenden en gegevens ontvangen . Als u het type bewerking wilt opgeven dat een kanaal heeft, moet u de kanaaloperator <-
gebruiken. Bovendien blokkeren het verzenden van gegevens en het ontvangen van gegevens in kanalen bewerkingen. Je zult zien waarom.
Als u wilt zeggen dat een kanaal alleen gegevens verzendt, gebruikt u de <-
operator na het kanaal. Wanneer u wilt dat het kanaal gegevens ontvangt, gebruikt u de <-
operator vóór het kanaal, zoals in de volgende voorbeelden:
ch <- x // sends (or writes ) x through channel ch
x = <-ch // x receives (or reads) data sent to the channel ch
<-ch // receives data, but the result is discarded
Een andere bewerking die u in een kanaal kunt gebruiken, is het sluiten ervan. Gebruik de ingebouwde close()
functie om een kanaal te sluiten:
close(ch)
Wanneer u een kanaal sluit, zegt u dat er geen gegevens meer in dat kanaal worden verzonden. Als u gegevens probeert te verzenden naar een gesloten kanaal, raakt het programma in paniek. En als u gegevens probeert te ontvangen van een gesloten kanaal, kunt u alle verzonden gegevens lezen. Elke volgende 'read' retourneert vervolgens een nulwaarde.
We gaan terug naar het programma dat we eerder hebben gemaakt en gebruiken kanalen om de slaapstandfunctionaliteit te verwijderen. Eerst gaan we een tekenreekskanaal maken in de main
functie, zoals hieronder:
ch := make(chan string)
En laten we de slaaplijn time.Sleep(3 * time.Second)
verwijderen.
Nu kunnen we kanalen gebruiken om te communiceren tussen goroutines. In plaats van het resultaat in de checkAPI
functie af te drukken, gaan we onze code herstructureren en dat bericht via het kanaal verzenden. Als u het kanaal van die functie wilt gebruiken, moet u het kanaal toevoegen als parameter. De checkAPI
functie moet er als volgt uitzien:
func checkAPI(api string, ch chan string) {
_, err := http.Get(api)
if err != nil {
ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
return
}
ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
U ziet dat we de fmt.Sprintf
functie moeten gebruiken omdat we geen tekst willen afdrukken, gewoon opgemaakte tekst over het kanaal verzenden. U ziet ook dat we de <-
operator na de kanaalvariabele gebruiken om gegevens te verzenden.
Nu moet u de main
functie wijzigen om de kanaalvariabele te verzenden en de gegevens te ontvangen om deze af te drukken, zoals deze:
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
U ziet hoe we de <-
operator gebruiken voordat het kanaal aangeeft dat we gegevens uit het kanaal willen lezen.
Wanneer u het programma opnieuw uitvoert, ziet u uitvoer zoals deze:
ERROR: https://api.somewhereintheinternet.com/ is down!
Done! It took 0.007401217 seconds!
Het werkt tenminste zonder een aanroep naar een slaapfunctie, toch? Maar het doet nog steeds niet wat we willen. We zien de uitvoer van een van de goroutines en we hebben er vijf gemaakt. Laten we eens kijken waarom dit programma op deze manier werkt in de volgende sectie.
Niet-gebufferde kanalen
Wanneer u een kanaal maakt met behulp van de make()
functie, maakt u een niet-gebufferd kanaal. Dit is het standaardgedrag. Niet-gebufferde kanalen blokkeren de verzendbewerking totdat iemand klaar is om de gegevens te ontvangen. Zoals we eerder hebben gezegd, blokkeren verzenden en ontvangen bewerkingen. Deze blokkeringsbewerking is ook waarom het programma uit de vorige sectie is gestopt zodra het het eerste bericht heeft ontvangen.
We kunnen beginnen met het zeggen dat fmt.Print(<-ch)
het programma wordt geblokkeerd omdat het wordt gelezen vanuit een kanaal en wacht tot sommige gegevens binnenkomen. Zodra het iets heeft, gaat het verder met de volgende regel en wordt het programma voltooid.
Wat is er gebeurd met de rest van de goroutines? Ze lopen nog steeds, maar niemand luistert meer. En omdat het programma vroeg is voltooid, konden sommige goroutines geen gegevens verzenden. Als u dit punt wilt bewijzen, gaan we een andere fmt.Print(<-ch)
toevoegen, zoals deze:
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
fmt.Print(<-ch)
Wanneer u het programma opnieuw uitvoert, ziet u uitvoer zoals deze:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
Done! It took 0.263611711 seconds!
U ziet nu de uitvoer voor twee API's. Als u meer fmt.Print(<-ch)
regels toevoegt, leest u alle gegevens die naar het kanaal worden verzonden. Maar wat gebeurt er als u meer gegevens probeert te lezen en niemand meer gegevens verzendt? Een voorbeeld hiervan is:
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
Wanneer u het programma opnieuw uitvoert, ziet u uitvoer zoals deze:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
SUCCESS: https://dev.azure.com is up and running!
Het werkt, maar het programma wordt niet voltooid. De laatste afdrukregel blokkeert deze omdat verwacht dat deze gegevens ontvangt. U moet het programma sluiten met een opdracht zoals Ctrl+C
.
Het vorige voorbeeld bewijst alleen dat het lezen van gegevens en het ontvangen van gegevens blokkerende bewerkingen zijn. U kunt dit probleem oplossen door de code te wijzigen in een for
lus en alleen de gegevens te ontvangen die u zeker weet te verzenden, zoals in dit voorbeeld:
for i := 0; i < len(apis); i++ {
fmt.Print(<-ch)
}
Hier volgt de definitieve versie van het programma voor het geval er iets mis is gegaan met uw versie:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
start := time.Now()
apis := []string{
"https://management.azure.com",
"https://dev.azure.com",
"https://api.github.com",
"https://outlook.office.com/",
"https://api.somewhereintheinternet.com/",
"https://graph.microsoft.com",
}
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
for i := 0; i < len(apis); i++ {
fmt.Print(<-ch)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
func checkAPI(api string, ch chan string) {
_, err := http.Get(api)
if err != nil {
ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
return
}
ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
Wanneer u het programma opnieuw uitvoert, ziet u uitvoer zoals deze:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
Done! It took 0.602099714 seconds!
Het programma doet wat het moet doen. U gebruikt geen slaapfunctie meer, u gebruikt kanalen. U ziet ook dat het nu ongeveer 600 ms duurt om te voltooien in plaats van bijna 2 seconden, toen we geen gelijktijdigheid gebruikten.
Ten slotte kunnen we zeggen dat niet-gebufferde kanalen de verzend- en ontvangstbewerkingen synchroniseren. Hoewel u gelijktijdigheid gebruikt, is de communicatie synchroon.