No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Mutex de lectura y escritura

4/19
Recursos

Aportes 7

Preguntas 2

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Solo para aclarar las diferencias entre RWLock y no usar nada:

  • Lock bloquea lecturas (con RLock) y escrituras (con Lock) de otras goroutines
  • Unlock permite nuevas lecturas (con Rlock) y/o otra escritura (con Lock)
  • RLock bloquea escrituras (Lock) pero no bloquea lecturas (RLock)
  • RUnlock permite nuevas escrituras (y también lecturas, pero por la naturaleza de RLock, estas no se vieron bloqueadas nunca)

En esencia, RLock de RWLock garantiza una secuencia de lecturas en donde el valor que lees no se verá alterado por nuevos escritores, a diferencia de no usar nada.

Sacado de aquí mero

Es mejor renombrar lock a mux para evitar lo que se conoce en Go como tartamudeo lock.Lock().

Refactoricé el ejemplo para que se muestre el balance en cada iteración del ciclo, se puede evidenciar que las lecturas se realizan mucho más rápido que las escrituras. Esto es esperable por tener muchas go routinas leyendo al mismo tiempo, pero solo una escribiendo a la vez.

La cuenta arranca con cero y va sumando i desde 1 hasta 20, segun la fórmula de Gauss la cuenta debe dar 210.

package main

import (
	"fmt"
	"sync"
)

var (
	balance int = 0
)

// Writer
func Deposit(amount int, wg *sync.WaitGroup, lock *sync.RWMutex) {
	defer wg.Done()
	defer lock.Unlock()

	lock.Lock()
	b := balance
	balance = b + amount
}

// Reader
func Balance(wg *sync.WaitGroup, lock *sync.RWMutex) {
	defer wg.Done()
	defer lock.RUnlock()
	lock.RLock()
	b := balance
	fmt.Println("Current balance is", b)
}

func main() {
	var wg sync.WaitGroup
	var lock sync.RWMutex

	for i := 1; i <= 20; i++ {
		wg.Add(1)
		go Deposit(i, &wg, &lock)
		wg.Add(1)
		go Balance(&wg, &lock)

	}

	wg.Wait()
	fmt.Println("Finished with balance", balance)
}

Obtiene como salida:

Current balance is 3
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 6
Current balance is 11
Current balance is 11
Current balance is 11
Current balance is 11
Current balance is 11
Current balance is 11
Current balance is 11
Finished with balance 210
package main

import "sync"

// Problem: Race condition
// build: go build --race main.go

var (
	balance int = 100
)

func Deposit(amount int, wg *sync.WaitGroup, lock *sync.RWMutex) {
	defer wg.Done()
	defer lock.Unlock() // Unlock is a method of the Mutex struct

	lock.Lock() // Lock the mutex
	b := balance
	balance = b + amount
}

func Balance(lock *sync.RWMutex) int {
	lock.RLock()
	b := balance
	lock.RUnlock()
	return b
}

// 1 Deposit() -> Escribir (Race Condition)
// N Balance() -> Leer

func main() {
	var wg sync.WaitGroup
	var lock sync.RWMutex // Mutex is a struct that implements the Lock and Unlock methods

	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go Deposit(i*100, &wg, &lock)
	}

	wg.Wait()
	println(Balance(&lock))
}

Hice un refactor del codigo imprimiendo en cada iteracion el balance para asi usar rwmutex *package* main *import* ( "fmt" "sync" ) *var* balance = 100 *func Deposit*(amount int, wg \*sync.WaitGroup, lock \*sync.RWMutex) { *defer* wg.Done() lock.Lock() b := balance balance = amount + b lock.Unlock() } *func Balance*(lock \*sync.RWMutex) int { lock.RLock() b := balance lock.RUnlock() *return* b } *func* main() { *var* wg sync.WaitGroup *var* lock sync.RWMutex *for* i := 1; i <= 5; i++ { wg.Add(1) *go* Deposit(i\*100, \&wg, \&lock) fmt.Println("The Current balance is: ", Balance(\&lock)) } wg.Wait() }

como podemos ver en este ejemplo, que como solo necesitamos en un deposito garantizar que para que comience una acción de deposito, la acciones que ejercen cambios en estas variables terminen su lógica antes de que comience de nuevo una acción de deposito. Por lo tanto esta función no la necesito modificar. el uso de Lock() y Unlock() es correcto.

Pero para el caso de Balance su lógica es distinta, Por lo que al bloquearlos con Lock() y Unlock() no permito ni la Escritura y Lectura. Quiere decir que mientras se este ejercicio alguna acción o cambios sobre su variable de lectura. No podrá ingresar a la info, para leerla mientras alguien se encuentre ejerciendo un deposito. Por lo tanto no estaríamos resolviendo el erro de dejarla como estaba sin el uso de Lock() y Unlock() en Balance(). Por que si no puedes a acceder a cambios de información actual y que el estado final dependa de que complete cierto bloque de código mas no solo una parte entonces puedo tomar un dato que no corresponda. Debido que no le estaríamos dando restricciones a Balance. Por lo tanto aplicamos restricciones respecto a las acciones.

func Balance(lock *sync.RWMutex) int {
	lock.RLock()
	b := balance   // solo estamos leyendo balance. por lo contrario en lo que pasa en Deposit.
	lock.RUnlock() // garantizo que mientras el valor termine de ejecutarse va aun mantener el estado anterior.
	return b
} 

Le estamos dando permisos de lectura mientras su permiso de escritura se encuentra bloqueado. Por lo que estamos permitiendo tener diferentes lectores sin que sean bloqueados.

A ver si entiendo. Yo elijo si el bloque es de lectura o de escritura según el sentido del código por supuesto.
Los bloques de codigo bloqueados con RLock bloquean a los bloques bloqueados con RWLock. Y los bloques bloqueados con RWLock bloquean todos los bloques que usen esa variable Mutex…