Sök…


Introduktion

I Go uppnås samtidighet genom användning av goroutiner, och kommunikation mellan goroutiner sker vanligtvis med kanaler. Men andra sätt att synkronisera, som mutexer och väntegrupper, finns tillgängliga och bör användas när de är mer praktiska än kanaler.

Syntax

  • go doWork () // kör funktionen doWork som en goroutine
  • ch: = make (chan int) // förklara ny kanal av typen int
  • ch <- 1 // skicka på en kanal
  • värde = <-ch // mottagande från en kanal

Anmärkningar

Goroutiner i Go liknar trådar på andra språk vad gäller användning. Internt skapar Go ett antal trådar (specificeras av GOMAXPROCS ) och planerar sedan goroutinerna att köras på trådarna. På grund av denna design är Gos samtidighetsmekanismer mycket effektivare än trådar när det gäller minnesanvändning och initialiseringstid.

Skapa goroutiner

Varje funktion kan åberopas som en goroutine genom att prefixera sin åkallelse med nyckelordet 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

Observera att funktionens returvärde ignoreras.

Hej värld Goroutine

singelkanal, enda goroutin, en skrivning, en läst.

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

Kör den på lekplatsen

Kanal ch är en obuffrad eller synkron kanal .

time.Sleep är här för att illustrera main() -funktionen kommer att väntach kanalen, vilket innebär att funktionen bokstavligen exekverad som en goroutine har tid att skicka ett värde genom den kanalen: mottagningsoperatören <-ch blockerar exekveringen av main() . Om det inte gjorde det, skulle goroutinen dödas när main() går ut och skulle inte ha tid att skicka sitt värde.

Väntar på goroutiner

Go program avslutas när main , därför är det vanligt att vänta på att alla goroutines till slut. En vanlig lösning för detta är att använda ett sync.WaitGroup- objekt.

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

Kör exemplet på lekplatsen

Vänta gruppanvändning i ordningsföljd:

  1. Förklaring av den globala variabeln. Att göra det globalt är det enklaste sättet att synliggöra alla funktioner och metoder.
  2. Öka räknaren. Detta måste göras i huvud goroutine eftersom det inte finns någon garanti för att ett nystartat goroutine kommer att utföra före fyra på grund av minnesmodellgarantier .
  3. Minska räknaren. Detta måste göras vid utgången av en goroutin. Genom att använda ett uppskjutet samtal ser vi till att det kommer att ringas när funktionen slutar , oavsett hur den slutar.
  4. Väntar på att räknaren når 0. Detta måste göras i huvudgoroutinen för att förhindra att programmet går ut innan alla goroutiner har slutat.

* Parametrar utvärderas innan en ny goroutin startas . Därför är det nödvändigt att definiera deras värden uttryckligen före wg.Add(10) så att eventuellt panikeringskod inte kommer att öka räknaren. Lägga till 10 objekt i WaitGroup, så det kommer att vänta i 10 artiklar innan wg.Wait returnerar kontrollen tillbaka till main() goroutine. Här definieras värdet på i i slingan för.

Använd stängningar med goroutiner i en slinga

I en slinga är slingvariabeln (val) i följande exempel en enda variabel som ändrar värde när den går över slingan. Därför måste man göra följande för att faktiskt överföra varje värdeval till goroutinen:

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

Om du skulle göra just gå func(val interface{}) { ... }() utan att passera val, då värdet på val kommer att vara vad val är när goroutines faktiskt körs.

Ett annat sätt att få samma effekt är:

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

Den konstigt snygga val := val skapar en ny variabel i varje iteration, som sedan nås av goroutinen.

Stoppa goroutiner

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 med två goroutiner

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

Kör en något modifierad version av denna kod i Go Playground



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow