Ricerca…


introduzione

In Go, la concorrenza viene raggiunta attraverso l'uso di goroutine e la comunicazione tra le goroutine viene solitamente eseguita con i canali. Tuttavia, sono disponibili altri mezzi di sincronizzazione, come mutex e wait groups, che dovrebbero essere utilizzati ogni volta che sono più convenienti dei canali.

Sintassi

  • go doWork () // esegue la funzione doWork come una goroutine
  • ch: = make (chan int) // dichiara un nuovo canale di tipo int
  • ch <- 1 // invio su un canale
  • value = <-ch // ricezione da un canale

Osservazioni

Le goroutine in Go sono simili alle discussioni in altre lingue in termini di utilizzo. Internamente, Go crea un numero di thread (specificato da GOMAXPROCS ) e quindi pianifica le goroutine per l'esecuzione sui thread. Grazie a questo design, i meccanismi di concorrenza di Go sono molto più efficienti dei thread in termini di utilizzo della memoria e tempo di inizializzazione.

Creazione di goroutine

Qualsiasi funzione può essere invocata come una goroutine mediante il prefisso della sua chiamata con la parola chiave 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

Si noti che il valore di ritorno della funzione è ignorato.

Ciao World Goroutine

canale singolo, goroutine singola, una scrittura, una lettura.

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

Eseguilo sul campo da gioco

Il canale ch è un canale senza buffer o sincrono .

Il time.Sleep è qui per illustrare la funzione main() aspetterà sul canale ch , che significa che la funzione letterale eseguita come una goroutine ha il tempo di inviare un valore attraverso quel canale: l' operatore di ricezione <-ch bloccherà l'esecuzione di main() . Se così non fosse, la goroutine verrebbe uccisa quando main() uscisse e non avrebbe il tempo di inviare il suo valore.

In attesa di goroutine

I programmi Go terminano quando termina la funzione main , quindi è prassi comune aspettare che tutte le goroutine finiscano. Una soluzione comune per questo è utilizzare un oggetto sync.WaitGroup .

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

Esegui l'esempio nel parco giochi

Uso WaitGroup in ordine di esecuzione:

  1. Dichiarazione di variabile globale. Rendendolo globale è il modo più semplice per renderlo visibile a tutte le funzioni e i metodi.
  2. Aumentare il contatore. Questo deve essere fatto nella goroutine principale perché non vi è alcuna garanzia che una goroutine appena avviata venga eseguita prima delle 4 a causa delle garanzie del modello di memoria.
  3. Diminuendo il contatore. Questo deve essere fatto all'uscita di una goroutine. Usando una chiamata differita, ci assicuriamo che venga chiamata ogni volta che la funzione termina , indipendentemente da come finisce.
  4. Aspettando che il contatore raggiunga 0. Questo deve essere fatto nella goroutine principale per impedire che il programma esca prima che tutte le goroutine siano finite.

* I parametri vengono valutati prima di iniziare una nuova goroutine . Quindi è necessario definire i loro valori esplicitamente prima di wg.Add(10) modo che il codice eventualmente panico non aumenti il ​​contatore. Aggiungendo 10 elementi al WaitGroup, quindi attenderà 10 elementi prima che wg.Wait restituisca il controllo alla goroutine main() . Qui, il valore di i è definito nel ciclo for.

Usando chiusure con goroutine in un ciclo

Quando si trova in un ciclo, la variabile di ciclo (val) nell'esempio seguente è una variabile singola che cambia valore mentre passa sopra il ciclo. Pertanto si deve fare quanto segue per passare effettivamente ogni val di valori alla goroutine:

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

Se dovessi fare solo go func(val interface{}) { ... }() senza passare val, allora il valore di val sarà qualunque cosa valga quando le goroutine vengono effettivamente eseguite.

Un altro modo per ottenere lo stesso effetto è:

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

val := val dall'aspetto strano val := val crea una nuova variabile in ogni iterazione, a cui accede la goroutine.

Fermare le goroutine

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 con due goroutine

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

Esegui una versione leggermente modificata di questo codice in Go Playground



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow