Introducción
Este laboratorio tiene como objetivo demostrar cómo utilizar canales y gorutinas para sincronizar el acceso a un estado compartido entre múltiples gorutinas.
This tutorial is from open-source community. Access the source code
💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí
Este laboratorio tiene como objetivo demostrar cómo utilizar canales y gorutinas para sincronizar el acceso a un estado compartido entre múltiples gorutinas.
En la programación concurrente, es esencial sincronizar el acceso al estado compartido para evitar condiciones de carrera y corrupción de datos. Este laboratorio presenta un escenario en el que una sola gorutina posee el estado y otras gorutinas envían mensajes para leer o escribir el estado.
readOp
y writeOp
para encapsular solicitudes y respuestas.resp
para indicar éxito y devolver valores.atomic
para contar operaciones de lectura y escritura.time
para agregar un retraso entre operaciones.## Ejecutar nuestro programa muestra que el ejemplo de
## administración de estado basado en gorutinas completa
## aproximadamente 80.000 operaciones en total.
$ go run stateful-goroutines.go
readOps: 71708
writeOps: 7177
## En este caso particular, el enfoque basado en gorutinas
## fue un poco más complejo que el basado en mutex.
## Puede ser útil en ciertos casos, por ejemplo, donde
## haya otros canales involucrados o cuando la gestión
## de múltiples mutexes sea propensa a errores. Debe
## utilizar el enfoque que sea más natural para usted,
## especialmente con respecto a la comprensión de la
## corrección de su programa.
A continuación está el código completo:
// En el ejemplo anterior utilizamos un bloqueo explícito con
// [mutexes](mutexes) para sincronizar el acceso al estado
// compartido entre múltiples gorutinas. Otra opción es
// utilizar las características de sincronización integradas
// de las gorutinas y los canales para obtener el mismo
// resultado. Este enfoque basado en canales está en
// línea con las ideas de Go de compartir memoria
// comunicándose y que cada pieza de datos sea propiedad
// de exactamente 1 gorutina.
package main
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
// En este ejemplo, nuestro estado será propiedad de una
// sola gorutina. Esto garantizará que los datos nunca se
// corrompan con el acceso concurrente. Para leer o
// escribir ese estado, otras gorutinas enviarán mensajes
// a la gorutina propietaria y recibirán respuestas
// correspondientes. Estos structs `readOp` y `writeOp`
// encapsulan esas solicitudes y una forma para que la
// gorutina propietaria responda.
type readOp struct {
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
func main() {
// Como antes, contaremos cuántas operaciones
// realizamos.
var readOps uint64
var writeOps uint64
// Los canales `reads` y `writes` serán utilizados por
// otras gorutinas para emitir solicitudes de lectura y
// escritura, respectivamente.
reads := make(chan readOp)
writes := make(chan writeOp)
// Aquí está la gorutina que posee el `estado`, que es
// un mapa como en el ejemplo anterior pero ahora
// privado a la gorutina con estado. Esta gorutina
// selecciona repetidamente en los canales `reads` y
// `writes`, respondiendo a las solicitudes a medida
// que llegan. Una respuesta se ejecuta primero
// realizando la operación solicitada y luego enviando
// un valor en el canal de respuesta `resp` para
// indicar éxito (y el valor deseado en el caso de
// `reads`).
go func() {
var state = make(map[int]int)
for {
select {
case read := <-reads:
read.resp <- state[read.key]
case write := <-writes:
state[write.key] = write.val
write.resp <- true
}
}
}()
// Esto inicia 100 gorutinas para emitir lecturas a la
// gorutina que posee el estado a través del canal
// `reads`. Cada lectura requiere construir un `readOp`,
// enviarlo a través del canal `reads` y luego recibir
// el resultado a través del canal `resp` proporcionado.
for r := 0; r < 100; r++ {
go func() {
for {
read := readOp{
key: rand.Intn(5),
resp: make(chan int)}
reads <- read
<-read.resp
atomic.AddUint64(&readOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
// También iniciamos 10 escrituras, utilizando un
// enfoque similar.
for w := 0; w < 10; w++ {
go func() {
for {
write := writeOp{
key: rand.Intn(5),
val: rand.Intn(100),
resp: make(chan bool)}
writes <- write
<-write.resp
atomic.AddUint64(&writeOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
// Deje que las gorutinas trabajen durante un segundo.
time.Sleep(time.Second)
// Finalmente, capture y reporte los conteos de
// operaciones.
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}
Este laboratorio demostró cómo utilizar canales y gorutinas para sincronizar el acceso a un estado compartido. Al tener una sola gorutina que posea el estado y utilizar canales para emitir solicitudes de lectura y escritura, podemos evitar condiciones de carrera y corrupción de datos.