DMarlon Studio

Lección 15 · 15 de 20

Mutex y estado compartido · Go

Proteja datos compartidos por varias goroutines en Go con sync.Mutex: 55 compradores, 40 entradas y ninguna vendida dos veces.

Go · Mutex y estado compartido en DMarlon Studio: el código del ejemplo y su salida en la terminal

Cuando varias goroutines leen y escriben la misma variable a la vez hay una condición de carrera. En Buy, comprobar si quedan entradas y descontar una son dos pasos: si dos compradores comprueban a la vez y ven 1, los dos la compran y se vende una entrada que no existe.

`sync.Mutex` (de *mutual exclusion*) lo impide. b.mu.Lock() deja pasar a una sola goroutine; las demás esperan en esa línea hasta que la primera llama a Unlock. Lo que queda entre las dos llamadas, la sección crítica, se ejecuta de una en una, así que comprobar y descontar ocurren juntos.

defer b.mu.Unlock() justo después de Lock es la forma habitual: el cerrojo se suelta al salir de la función, salga como salga. El mutex vive dentro del struct, junto a los datos que protege, y el comentario lo dice. Los métodos usan un receptor puntero: copiar un Mutex lo estropea (y go vet lo avisa).

En main, 55 goroutines compran a la vez y wg.Wait() espera a todas. Leer office.sold después sin el cerrojo es correcto, porque Wait garantiza que todas han terminado. El resultado no depende del orden: siempre se venden 40, se rechazan 15 y no queda ninguna.

Para detectar carreras, ejecute go run -race main.go: avisa si dos goroutines tocan lo mismo sin protección. Si casi todo son lecturas, existe sync.RWMutex; para un simple contador, sync/atomic.

Código · main.go

package mainimport (	"fmt"	"sync")type BoxOffice struct { // shared by many goroutines: mu protects the fields below	mu      sync.Mutex	tickets map[string]int	sold    int}func (b *BoxOffice) Buy(show string) {	b.mu.Lock()              // only one goroutine at a time gets past Lock	defer b.mu.Unlock()      // released when Buy returns	if b.tickets[show] > 0 { // check and update together: no ticket is sold twice		b.tickets[show]--		b.sold++	}}func main() {	office := &BoxOffice{tickets: map[string]int{"jazz night": 40}}	var wg sync.WaitGroup	for i := 0; i < 55; i++ { // 55 buyers arrive at the same time		wg.Add(1)		go func() { defer wg.Done(); office.Buy("jazz night") }()	}	wg.Wait()	fmt.Println("buyers: 55 | sold:", office.sold, "| turned away:", 55-office.sold)	fmt.Println("tickets left:", office.tickets["jazz night"])}

Salida · go run main.go

buyers: 55 | sold: 40 | turned away: 15
tickets left: 0

Para recordar

  • Dos goroutines que escriben lo mismo sin protección son una carrera.
  • Lock deja pasar a una sola goroutine hasta el Unlock.
  • Escriba defer mu.Unlock() justo después de Lock().
  • Ponga el mutex junto a los datos que protege y use receptor puntero.
  • go run -race detecta condiciones de carrera.

Ejercicio

Añada a la taquilla un segundo concierto, "rock night", con 10 entradas, y haga que los compradores pares pidan uno y los impares el otro. Escriba al final lo que queda de cada uno.