Recherche…


Introduction

Dans Go, la simultanéité est obtenue grâce à l'utilisation de goroutines, et la communication entre les goroutines se fait généralement avec des canaux. Cependant, d'autres moyens de synchronisation, tels que les mutex et les groupes d'attente, sont disponibles et doivent être utilisés chaque fois qu'ils sont plus pratiques que les canaux.

Syntaxe

  • aller doWork () // exécuter la fonction doWork en tant que goroutine
  • ch: = make (chan int) // déclare le nouveau canal de type int
  • ch <- 1 // envoi sur un canal
  • value = <-ch // recevoir d'un canal

Remarques

Goroutines in Go sont similaires aux threads dans d'autres langues en termes d'utilisation. En interne, Go crée un certain nombre de threads (spécifiés par GOMAXPROCS ), puis planifie l'exécution des goroutines sur les threads. En raison de cette conception, les mécanismes de concurrence de Go sont beaucoup plus efficaces que les threads en termes d'utilisation de la mémoire et de temps d'initialisation.

Créer des goroutines

Toute fonction peut être appelée en tant que goroutine en préfixant son invocation avec le mot go clé 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

Notez que la valeur de retour de la fonction est ignorée.

Bonjour tout le monde Goroutine

single channel, single goroutine, one write, one read.

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

Exécuter sur le terrain de jeu

Le canal ch est un canal non tamponné ou synchrone .

Le time.Sleep est ici pour illustrer main() fonction main() qui attendra sur le canal ch , ce qui signifie que la fonction littérale exécutée en tant que goroutine a le temps d’envoyer une valeur via ce canal: l’ opérateur de réception <-ch va bloquer main() . Si ce n'était pas le cas, le goroutine serait tué lorsque main() quitterait et n'aurait pas le temps d'envoyer sa valeur.

En attente de goroutines

Les programmes Go se terminent lorsque la fonction main se termine , il est donc courant d'attendre que toutes les goroutines se terminent. Une solution courante consiste à utiliser un objet 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")
}

Exécuter l'exemple dans la cour de récréation

WaitGroup utilisation dans l'ordre d'exécution:

  1. Déclaration de variable globale. Le rendre global est le moyen le plus simple de le rendre visible à toutes les fonctions et méthodes.
  2. Augmenter le compteur Cela doit être fait dans la base de données principale car rien ne garantit qu'une goroutine nouvellement démarrée s'exécutera avant 4 en raison des garanties du modèle de mémoire.
  3. Diminuer le compteur Cela doit être fait à la sortie d'une goroutine. En utilisant un appel différé, nous nous assurons qu'il sera appelé chaque fois que la fonction se terminera , peu importe comment elle se termine.
  4. Attendre que le compteur atteigne 0. Cela doit être fait dans la liste principale pour empêcher le programme de sortir avant que toutes les goroutines soient terminées.

* Les paramètres sont évalués avant de lancer un nouveau goroutine . Il est donc nécessaire de définir explicitement leurs valeurs avant wg.Add(10) afin que le code éventuellement paniqué n'augmente pas le compteur. En ajoutant 10 éléments au WaitGroup, il faudra attendre 10 éléments avant que wg.Wait ramène le contrôle à main() goroutine. Ici, la valeur de i est définie dans la boucle for.

Utiliser des fermetures avec des goroutines en boucle

Lorsqu'elle est en boucle, la variable de boucle (val) dans l'exemple suivant est une variable unique qui change de valeur lorsqu'elle passe sur la boucle. Par conséquent, il faut faire ce qui suit pour que chaque valeur de valeur soit transmise au goroutine:

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

Si vous deviez faire juste aller func(val interface{}) { ... }() sans passer val, alors la valeur de val sera n'importe quel val est quand les goroutines fonctionne réellement.

Une autre façon d'obtenir le même effet est la suivante:

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

Le val := val étrange val := val crée une nouvelle variable dans chaque itération, à laquelle le goroutine accède ensuite.

Arrêt des goroutines

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

Exécuter une version légèrement modifiée de ce code dans Go Playground



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow