Go
Concurrencia
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)
}
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:
- Declaración de variable global. Hacerlo global es la forma más fácil de hacerlo visible a todas las funciones y métodos.
- 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.
- 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.
- 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