Buscar..


Introducción

En Go, la concurrencia se logra mediante el uso de goroutines, y la comunicación entre goroutines generalmente se realiza con canales. Sin embargo, otros medios de sincronización, como las exclusiones mutuas y los grupos de espera, están disponibles, y deberían usarse siempre que sean más convenientes que los canales.

Sintaxis

  • go doWork () // ejecuta la función doWork como una goroutine
  • ch: = make (chan int) // declara nuevo canal de tipo int
  • ch <- 1 // envío en un canal
  • valor = <-ch // recibiendo de un canal

Observaciones

Goroutines en Go son similares a los hilos en otros idiomas en términos de uso. Internamente, Go crea una serie de subprocesos (especificados por GOMAXPROCS ) y luego programa los goroutines para que se ejecuten en los subprocesos. Debido a este diseño, los mecanismos de concurrencia de Go son mucho más eficientes que los hilos en términos de uso de memoria y tiempo de inicialización.

Creando goroutines

Cualquier función se puede invocar como una goroutina prefijando su invocación con la palabra clave 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

Tenga en cuenta que el valor de retorno de la función se ignora.

Hola mundo goroutine

Un solo canal, un solo goroutine, una escritura, una lectura.

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

Ejecutalo en el patio

El canal ch es un canal sin buffer o sincrónico .

El time.Sleep está aquí para ilustrar la función main() esperará en el canal ch , lo que significa que la función literal ejecutada como goroutine tiene tiempo para enviar un valor a través de ese canal: el operador de recepción <-ch bloqueará la ejecución de main() . Si no fuera así, la goroutina se eliminaría cuando main() salga y no tendría tiempo de enviar su valor.

Esperando goroutines

Los programas de Go terminan cuando finaliza la función main , por lo que es una práctica común esperar a que todos los goroutines terminen. Una solución común para esto es usar un objeto 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")
}

Ejecutar el ejemplo en el patio de recreo.

Uso de WaitGroup en orden de ejecución:

  1. Declaración de variable global. Hacerlo global es la forma más fácil de hacerlo visible a todas las funciones y métodos.
  2. Aumentar el contador. Esto debe hacerse en la goroutine principal porque no hay garantía de que una goroutine recién iniciada se ejecute antes de las 4 debido a las garantías del modelo de memoria.
  3. Disminuyendo el contador. Esto debe hacerse a la salida de una goroutina. Al utilizar una llamada diferida, nos aseguramos de que se llamará cada vez que la función finalice , sin importar cómo termine.
  4. Esperando que el contador llegue a 0. Esto se debe hacer en la goroutine principal para evitar que el programa salga antes de que todos los goroutines hayan terminado.

* Los parámetros son evaluados antes de comenzar una nueva goroutina . Por lo tanto, es necesario definir sus valores explícitamente antes de wg.Add(10) para que el código de posible pánico no incremente el contador. Añadiendo 10 elementos al WaitGroup, por lo que esperará 10 elementos antes de que wg.Wait devuelva el control a main() goroutine. Aquí, el valor de i se define en el bucle for.

Usando cierres con goroutines en un bucle.

Cuando está en un bucle, la variable de bucle (val) en el siguiente ejemplo es una variable única que cambia de valor a medida que pasa por el bucle. Por lo tanto, uno debe hacer lo siguiente para pasar realmente cada valor de valores al goroutine:

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

Si tuviera que hacer solo go func(val interface{}) { ... }() sin pasar val, entonces el valor de val será el valor de val cuando se ejecuten los goroutines.

Otra forma de obtener el mismo efecto es:

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

El val := val crea una nueva variable en cada iteración, a la que luego accede la goroutina.

Detener 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 con dos 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)
    }
}

Ejecuta una versión ligeramente modificada de este código en Go Playground



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow