Meer informatie over goroutines

Voltooid

Gelijktijdigheid is de samenstelling van onafhankelijke activiteiten, zoals het werk dat een webserver doet wanneer deze meerdere gebruikersaanvragen tegelijk afhandelt, maar op een autonome manier. Gelijktijdigheid is tegenwoordig aanwezig in veel programma's. Webservers zijn één voorbeeld, maar u ziet ook dat gelijktijdigheid nodig is bij het verwerken van aanzienlijke hoeveelheden gegevens in batches.

Go heeft twee stijlen voor het schrijven van gelijktijdige programma's. Een is de traditionele stijl die u mogelijk in andere talen met threads hebt gebruikt. In deze module leert u meer over go's stijl, waarbij waarden worden doorgegeven tussen onafhankelijke activiteiten die goroutines worden genoemd om processen te communiceren.

Als u voor het eerst leert over gelijktijdigheid, raden we u aan om wat extra tijd te besteden aan het controleren van elk stukje code dat we schrijven om te oefenen.

Go's benadering van gelijktijdigheid

Normaal gesproken is het grootste probleem bij het schrijven van gelijktijdige programma's het delen van gegevens tussen processen. Go heeft een andere benadering dan andere programmeertalen met communicatie, omdat Go gegevens heen en weer doorstuurt via kanalen. Deze benadering betekent dat slechts één activiteit (goroutine) toegang heeft tot de gegevens en dat er geen racevoorwaarde is. Tijdens het leren over goroutines en kanalen in deze module krijgt u meer inzicht in de gelijktijdigheidsbenadering van Go.

De benadering van Go kan worden samengevat in de volgende slogan: "Niet communiceren door geheugen te delen; deel in plaats daarvan geheugen door te communiceren." We behandelen deze aanpak in de volgende secties, maar u kunt ook meer informatie vinden in het Go-blogbericht Geheugen delen door te communiceren.

Zoals eerder vermeld, bevat Go ook gelijktijdigheidsprimitief op laag niveau. Maar we behandelen go's idiomatische benadering alleen voor gelijktijdigheid in deze module.

Laten we beginnen met het verkennen van goroutines.

Goroutines

Een goroutine is een gelijktijdige activiteit in een lichtgewicht thread, niet de traditionele activiteit die u in een besturingssysteem hebt. Stel dat u een programma hebt dat naar de uitvoer schrijft en een andere functie waarmee bijvoorbeeld twee getallen worden berekend. Een gelijktijdig programma kan verschillende goroutines hebben die beide functies tegelijkertijd aanroepen.

We kunnen zeggen dat de eerste goroutine die een programma uitvoert de main() functie is. Als u een andere goroutine wilt maken, moet u het go trefwoord gebruiken voordat u de functie aanroept, zoals in dit voorbeeld:

func main(){
    login()
    go launch()
}

U zult ook merken dat veel programma's graag anonieme functies gebruiken om goroutines te maken, zoals in deze code:

func main(){
    login()
    go func() {
        launch()
    }()
}

Laten we een gelijktijdig programma schrijven om goroutines in actie te zien.

Een gelijktijdig programma schrijven

Omdat we ons alleen willen richten op het gelijktijdige deel, gebruiken we een bestaand programma dat controleert of een API-eindpunt reageert of niet. Hier volgt de code:

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())
}

Wanneer u de voorgaande code uitvoert, krijgt u de volgende uitvoer:

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!

Niets is hier gewoon, maar we kunnen beter. Misschien kunnen we alle sites tegelijk controleren? In plaats van bijna twee seconden te nemen, kan het programma in minder dan 500 ms eindigen.

U ziet dat het gedeelte van de code dat we gelijktijdig moeten uitvoeren, het gedeelte is dat de HTTP-aanroep naar de site uitvoert. Met andere woorden, we moeten een goroutine maken voor elke API die door het programma wordt gecontroleerd.

Als u een goroutine wilt maken, moet u het go trefwoord gebruiken voordat u een functie aanroept. Maar daar hebben we geen functie. Laten we die code herstructureren en een nieuwe functie maken, zoals deze:

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)
}

U ziet dat we het continue trefwoord niet meer nodig hebben omdat we niet in een for lus zitten. We gebruiken het return trefwoord om de uitvoeringsstroom van de functie te stoppen. Nu moeten we de code in de main() functie wijzigen om een goroutine per API te maken, zoals deze:

for _, api := range apis {
    go checkAPI(api)
}

Voer het programma opnieuw uit en kijk wat er gebeurt.

Het lijkt erop dat het programma de API's niet meer controleert, toch? Mogelijk ziet u ongeveer de volgende uitvoer:

Done! It took 1.506e-05 seconds!

Dat was snel! Wat is er gebeurd? U ziet het laatste bericht waarin staat dat het programma is voltooid omdat Go een goroutine heeft gemaakt voor elke site binnen de lus en dat het direct naar de volgende regel is gegaan.

Hoewel de functie er niet uitziet als de checkAPI functie wordt uitgevoerd, wordt deze uitgevoerd. Het had gewoon geen tijd om af te ronden. Let op wat er gebeurt als u direct na de lus een slaaptimer opneemt, zoals deze:

for _, api := range apis {
    go checkAPI(api)
}

time.Sleep(3 * time.Second)

Wanneer u het programma opnieuw uitvoert, ziet u mogelijk een 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://outlook.office.com/ is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 3.002114573 seconds!

Het lijkt erop dat het werkt, toch? Nou, niet precies. Wat moet u doen als u een nieuwe site wilt toevoegen aan de lijst? Misschien zijn drie seconden niet genoeg. Hoe zou je het weten? Dat is niet mogelijk. Er moet een betere manier zijn, en dat bespreken we in de volgende sectie wanneer we het hebben over kanalen.