Lär dig mer om goroutines
Samtidighet är sammansättningen av oberoende aktiviteter, som det arbete som en webbserver utför när den hanterar flera användarbegäranden samtidigt men på ett autonomt sätt. Samtidighet finns i många program idag. Webbservrar är ett exempel, men du ser också behovet av samtidighet vid bearbetning av stora mängder data i batchar.
Go har två format för att skriva samtidiga program. Det ena är det traditionella format som du kan ha använt på andra språk med trådar. I den här modulen får du lära dig mer om Gos stil, där värden skickas mellan oberoende aktiviteter som kallas goroutiner för att kommunicera processer.
Om du lär dig om samtidighet för första gången rekommenderar vi att du lägger lite extra tid på att granska varje kod som vi skriver för att öva.
Gos metod för samtidighet
Vanligtvis är det största problemet med att skriva samtidiga program att dela data mellan processer. Go använder en annan metod än andra programmeringsspråk med kommunikation, eftersom Go skickar data fram och tillbaka via kanaler. Den här metoden innebär att endast en aktivitet (goroutine) har åtkomst till data och att det inte finns något konkurrenstillstånd avsiktligt. När du lär dig mer om goroutines och kanaler i den här modulen får du bättre förståelse för Gos samtidighetsmetod.
Gos metod kan sammanfattas i följande slogan: "Kommunicera inte genom att dela minne; dela i stället minne genom att kommunicera." Vi går igenom den här metoden i följande avsnitt, men du kan också läsa mer i Go-blogginlägget Dela minne genom att kommunicera.
Som vi nämnde tidigare innehåller Go även primitiver med låg samtidighet. Men vi tar bara upp Gos idiomatiska metod för samtidighet i den här modulen.
Vi börjar med att utforska goroutines.
Goroutines
En goroutin är en samtidig aktivitet i en lätt tråd, inte den traditionella som du har i ett operativsystem. Anta att du har ett program som skriver till utdata och en annan funktion som beräknar saker som att lägga till två tal. Ett samtidigt program kan ha flera goroutines som anropar båda funktionerna samtidigt.
Vi kan säga att den första goroutin som ett program kör är main()
funktionen. Om du vill skapa en annan goroutin måste du använda nyckelordet go
innan du anropar funktionen, som i det här exemplet:
func main(){
login()
go launch()
}
Du kan också se att många program gillar att använda anonyma funktioner för att skapa goroutines, som i den här koden:
func main(){
login()
go func() {
launch()
}()
}
Om du vill se goroutines i praktiken ska vi skriva ett samtidigt program.
Skriva ett samtidigt program
Eftersom vi bara vill fokusera på den samtidiga delen ska vi använda ett befintligt program som kontrollerar om en API-slutpunkt svarar eller inte. Här är koden:
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",
}
for _, api := range apis {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
continue
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
När du kör föregående kod får du följande utdata:
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 1.658436834 seconds!
Inget är ovanligt här, men vi kan göra bättre. Vi kanske kan kontrollera alla webbplatser samtidigt? I stället för att ta nästan två sekunder kan programmet avslutas på mindre än 500 ms.
Observera att den del av koden som vi behöver köra samtidigt är den som gör HTTP-anropet till webbplatsen. Med andra ord måste vi skapa en goroutine för varje API som programmet kontrollerar.
För att skapa en goroutine måste vi använda nyckelordet go
innan vi anropar en funktion. Men vi har ingen funktion där. Vi omstrukturerar koden och skapar en ny funktion, så här:
func checkAPI(api string) {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
return
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
Observera att vi inte behöver nyckelordet continue
längre eftersom vi inte är i en for
loop. För att stoppa körningsflödet för funktionen använder vi nyckelordet return
. Nu måste vi ändra koden i main()
funktionen för att skapa en goroutine per API, så här:
for _, api := range apis {
go checkAPI(api)
}
Kör programmet igen och se vad som händer.
Det verkar som om programmet inte kontrollerar API:erna längre, eller hur? Du kan se något som liknar följande utdata:
Done! It took 1.506e-05 seconds!
Det var snabbt! Vad hände? Du ser det sista meddelandet som säger att programmet har slutförts eftersom Go skapade en goroutine för varje plats i loopen, och det gick omedelbart till nästa rad.
Även om det inte ser ut som om funktionen körs körs den checkAPI
. Det hade bara inte tid att avsluta. Observera vad som händer om du inkluderar en vilotimer direkt efter loopen, så här:
for _, api := range apis {
go checkAPI(api)
}
time.Sleep(3 * time.Second)
Nu när du kör programmet igen kan du se utdata så här:
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://outlook.office.com/ is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 3.002114573 seconds!
Det verkar fungera, eller hur? Inte exakt. Vad händer om du vill lägga till en ny webbplats i listan? Kanske räcker det inte med tre sekunder. Hur skulle du veta det? Det kan du inte. Det måste finnas ett bättre sätt, och det är vad vi kommer att diskutera i nästa avsnitt när vi pratar om kanaler.