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?

o inicia sesi贸n.

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鈥