Zoeken…


Invoering

In Go wordt gelijktijdigheid bereikt door het gebruik van goroutines, en communicatie tussen goroutines gebeurt meestal via kanalen. Andere synchronisatiemiddelen, zoals mutexen en wachtgroepen, zijn echter beschikbaar en moeten worden gebruikt wanneer ze handiger zijn dan kanalen.

Syntaxis

  • go doWork () // voer de functie doWork uit als een goroutine
  • ch: = make (chan int) // verklaar nieuw kanaal van type int
  • ch <- 1 // verzenden op een kanaal
  • waarde = <-ch // ontvangen van een kanaal

Opmerkingen

Goroutines in Go zijn qua gebruik vergelijkbaar met threads in andere talen. Intern maakt Go een aantal threads (gespecificeerd door GOMAXPROCS ) en GOMAXPROCS vervolgens de goroutines om op de threads te worden uitgevoerd. Vanwege dit ontwerp zijn Go's gelijktijdigheidsmechanismen veel efficiënter dan threads in termen van geheugengebruik en initialisatietijd.

Goroutines maken

Elke functie kan worden opgeroepen als een goroutine door de aanroep vooraf te go met het trefwoord go :

func DoMultiply(x,y int) {
    // Simulate some hard work
    time.Sleep(time.Second * 1)
    fmt.Printf("Result: %d\n", x * y)
}

go DoMultiply(1,2) // first execution, non-blocking
go DoMultiply(3,4) // second execution, also non-blocking

// Results are printed after a single second only, 
// not 2 seconds because they execute concurrently:
// Result: 2
// Result: 12

Merk op dat de retourwaarde van de functie wordt genegeerd.

Hallo wereld Goroutine

één kanaal, één goroutine, één schrijven, één lezen.

package main

import "fmt"
import "time"

func main() {
    // create new channel of type string
    ch := make(chan string)

    // start new anonymous goroutine
    go func() {
        time.Sleep(time.Second)
        // send "Hello World" to channel
        ch <- "Hello World"
    }()
    // read from channel
    msg, ok := <-ch
    fmt.Printf("msg='%s', ok='%v'\n", msg, ok)
}

Voer het uit op een speelplaats

Het kanaal ch is een ongebufferd of synchroon kanaal .

De time.Sleep is hier ter illustratie main() functie wachten op ch kanaal, dat de middelen functioneren letterlijke uitgevoerd als goroutine heeft de tijd om een waarde te sturen via dat kanaal: de ontvangen operator <-ch zullen de prestaties blokkeren main() . Als dit niet het geval was, zou de goroutine worden gedood wanneer main() afgesloten en zou hij geen tijd hebben om zijn waarde te verzenden.

Wachten op goroutines

Go programma eindigt wanneer de main functie eindigt , daarom is het gebruikelijk om te wachten op alle goroutines tot finish. Een gebruikelijke oplossing hiervoor is het gebruik van een sync.WaitGroup- object.

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup // 1

func routine(i int) {
    defer wg.Done() // 3
    fmt.Printf("routine %v finished\n", i)
}

func main() {
    wg.Add(10) // 2
    for i := 0; i < 10; i++ {
        go routine(i) // *
    }
    wg.Wait() // 4
    fmt.Println("main finished")
}

Voer het voorbeeld uit in de speeltuin

WaitGroup-gebruik in volgorde van uitvoering:

  1. Verklaring van globale variabele. Wereldwijd maken is de gemakkelijkste manier om het zichtbaar te maken voor alle functies en methoden.
  2. De teller verhogen. Dit moet gebeuren in de voornaamste goroutine want er is geen garantie dat een nieuw gestart goroutine zal uitvoeren vóór 4 te wijten aan het geheugen model garanties .
  3. De teller verlagen. Dit moet worden gedaan bij de uitgang van een goroutine. Door een uitgestelde oproep te gebruiken, zorgen we ervoor dat deze wordt aangeroepen wanneer de functie eindigt , ongeacht hoe deze eindigt.
  4. Wachten tot de teller op 0 komt. Dit moet in de hoofd-goroutine worden gedaan om te voorkomen dat het programma wordt afgesloten voordat alle goroutines klaar zijn.

* Parameters worden geëvalueerd voordat een nieuwe goroutine wordt gestart . Het is dus noodzakelijk om hun waarden expliciet vóór wg.Add(10) te definiëren wg.Add(10) zodat mogelijk wg.Add(10) de teller niet verhoogt. 10 items toevoegen aan de WaitGroup, zodat deze 10 items wacht voordat wg.Wait het besturingselement terugzet naar main() goroutine. Hier wordt de waarde van i gedefinieerd in de for-lus.

Sluitingen gebruiken met goroutines in een lus

In een lus is de lusvariabele (val) in het volgende voorbeeld een enkele variabele die van waarde verandert terwijl deze over de lus gaat. Daarom moet men het volgende doen om elke val van waarden daadwerkelijk aan de goroutine door te geven:

for val := range values {
    go func(val interface{}) {
        fmt.Println(val)
    }(val)
}

Als je gewoon moet gaan func(val interface{}) { ... }() zonder val door te geven, dan zal de waarde van val zijn wat val is wanneer de goroutines daadwerkelijk worden uitgevoerd.

Een andere manier om hetzelfde effect te krijgen is:

for val := range values {
    val := val
    go func() {
        fmt.Println(val)
    }()
}

De vreemd uitziende val := val maakt een nieuwe variabele in elke iteratie, die vervolgens wordt geopend door de goroutine.

Goroutines stoppen

package main

import (
    "log"
    "sync"
    "time"
)

func main() {
    // The WaitGroup lets the main goroutine wait for all other goroutines
    // to terminate. However, this is no implicit in Go. The WaitGroup must
    // be explicitely incremented prior to the execution of any goroutine 
    // (i.e. before the `go` keyword) and it must be decremented by calling
    // wg.Done() at the end of every goroutine (typically via the `defer` keyword). 
    wg := sync.WaitGroup{}

    // The stop channel is an unbuffered channel that is closed when the main
    // thread wants all other goroutines to terminate (there is no way to 
    // interrupt another goroutine in Go). Each goroutine must multiplex its
    // work with the stop channel to guarantee liveness.
    stopCh := make(chan struct{})


    for i := 0; i < 5; i++ {
        // It is important that the WaitGroup is incremented before we start
        // the goroutine (and not within the goroutine) because the scheduler
        // makes no guarantee that the goroutine starts execution prior to 
        // the main goroutine calling wg.Wait().
        wg.Add(1)
        go func(i int, stopCh <-chan struct{}) {
            // The defer keyword guarantees that the WaitGroup count is 
            // decremented when the goroutine exits.
            defer wg.Done()

            log.Printf("started goroutine %d", i)

            select {
            // Since we never send empty structs on this channel we can 
            // take the return of a receive on the channel to mean that the
            // channel has been closed (recall that receive never blocks on
            // closed channels).   
            case <-stopCh:
                log.Printf("stopped goroutine %d", i)
            }
        }(i, stopCh)
    }

    time.Sleep(time.Second * 5)
    close(stopCh)
    log.Printf("stopping goroutines")
    wg.Wait()
    log.Printf("all goroutines stopped")
}

Ping pong met twee goroutines

package main

import (
    "fmt"
    "time"
)

// The pinger prints a ping and waits for a pong
func pinger(pinger <-chan int, ponger chan<- int) {
    for {
        <-pinger
        fmt.Println("ping")
        time.Sleep(time.Second)
        ponger <- 1
    }
}

// The ponger prints a pong and waits for a ping
func ponger(pinger chan<- int, ponger <-chan int) {
    for {
        <-ponger
        fmt.Println("pong")
        time.Sleep(time.Second)
        pinger <- 1
    }
}

func main() {
    ping := make(chan int)
    pong := make(chan int)

    go pinger(ping, pong)
    go ponger(ping, pong)

    // The main goroutine starts the ping/pong by sending into the ping channel
    ping <- 1

    for {
        // Block the main thread until an interrupt
        time.Sleep(time.Second)
    }
}

Voer een enigszins gewijzigde versie van deze code uit in Go Playground



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow