Investigamos en todo el mundo online para regalarte la respuesta para tu dilema, si tienes alguna pregunta puedes dejar tu pregunta y te contestamos sin falta, porque estamos para ayudarte.
Solución:
synchronized
en Java es un medio para permitir que un solo hilo ejecute un bloque de código (en un momento dado).
En Go hay numerosas construcciones para lograrlo (por ejemplo, mutex, canales, grupos de espera, primitivas en sync/atomic
), pero el proverbio de Go es: “No se comunique compartiendo la memoria; en cambio, comparta la memoria comunicándose”.
Entonces, en lugar de bloquear y compartir una variable, intente no hacer eso, sino que comunique el resultado entre gorutinas, por ejemplo, usando canales (para que no tenga que acceder a la memoria compartida). Para obtener más información, consulte The Go Blog: Comparta la memoria comunicándose.
Por supuesto, puede haber casos en los que la solución directa más simple sea usar un mutex para proteger el acceso concurrente de múltiples goroutines a una variable. Cuando este es el caso, así es como puede hacer eso:
var (
mu sync.Mutex
protectMe int
)
func getMe() int
mu.Lock()
me := protectMe
mu.Unlock()
return me
func setMe(me int)
mu.Lock()
protectMe = me
mu.Unlock()
La solución anterior podría mejorarse en varias áreas:
-
Usar
sync.RWMutex
en vez desync.Mutex
, de manera que lagetMe()
puede bloquearse solo para lectura, por lo que varios lectores simultáneos no se bloquearían entre sí. -
Después de un bloqueo (exitoso), es aconsejable desbloquear usando
defer
, por lo que si sucede algo malo en el código posterior (por ejemplo, pánico en tiempo de ejecución), el mutex seguirá desbloqueado, evitando pérdidas de recursos y puntos muertos. Aunque este ejemplo es tan simple, no puede pasar nada malo y no justifica el uso incondicional del desbloqueo diferido. -
Es una buena práctica mantener el mutex cerca de los datos que debe proteger. Así que “envolviendo”
protectMe
y esmu
en una estructura es una buena idea. Y si estamos en eso, también podemos usar la incrustación, por lo que bloquear / desbloquear se vuelve más conveniente (a menos que esta funcionalidad no deba exponerse). Para obtener más información, consulte ¿Cuándo se inserta mutex en la estructura en Go?
Entonces, una versión mejorada del ejemplo anterior podría verse así (pruébelo en Go Playground):
type Me struct
sync.RWMutex
me int
func (m *Me) Get() int
m.RLock()
m.RUnlock()
return m.me
func (m *Me) Set(me int)
m.Lock()
m.me = me
m.Unlock()
var me = &Me
func main()
me.Set(2)
fmt.Println(me.Get())
Esta solución tiene otra ventaja: si necesita varios valores de Me
, automáticamente tendrá diferentes mutexs separados para cada valor (nuestra solución inicial requeriría crear mutexs separados manualmente para cada nuevo valor).
Aunque este ejemplo es correcto y válido, puede que no sea práctico. Porque proteger un solo entero no requiere realmente un mutex. Podríamos lograr lo mismo usando el sync/atomic
paquete:
var protectMe int32
func getMe() int32
return atomic.LoadInt32(&protectMe)
func setMe(me int32)
atomic.StoreInt32(&protectMe, me)
Esta solución es más corta, más limpia y más rápida. Si su objetivo es proteger solo un valor, se prefiere esta solución. Si la estructura de datos que debe proteger es más compleja, atomic
puede que ni siquiera sea viable, y el uso de un mutex podría estar justificado.
Ahora, después de mostrar ejemplos de compartir / proteger variables, también deberíamos dar un ejemplo de lo que deberíamos aspirar a lograr para estar a la altura “No se comunique compartiendo la memoria; en cambio, comparta la memoria comunicándose”.
La situación es que tiene múltiples goroutines concurrentes y usa una variable donde almacena algún estado. Una goroutine cambia (establece) el estado y otra lee (obtiene) el estado. Para acceder a este estado desde múltiples goroutines, el acceso debe estar sincronizado.
Y la idea es no tener una variable “compartida” como esta, sino el estado que establecería una goroutine, debería “enviar” en su lugar, y la otra goroutine que lo leería, debería ser a la que se “envía” el estado (o en otras palabras, la otra goroutine debería recibir el estado cambiado). Entonces no hay una variable de estado compartida, en su lugar hay una comunicación entre las 2 gorutinas. Go proporciona un excelente soporte para este tipo de comunicación “inter-rutinaria”: canales. El soporte para canales está integrado en el idioma, hay declaraciones de envío, operadores de recepción y otro soporte (por ejemplo, puede recorrer los valores enviados en un canal). Para una introducción y detalles, marque esta respuesta: ¿Para qué se utilizan los canales?
Veamos un ejemplo práctico / de la vida real: un “corredor”. Un corredor es una entidad donde los “clientes” (goroutines) pueden suscribirse para recibir mensajes / actualizaciones, y el corredor es capaz de transmitir mensajes a los clientes suscritos. En un sistema donde hay numerosos clientes que pueden suscribirse / darse de baja en cualquier momento, y puede haber la necesidad de transmitir mensajes en cualquier momento, sincronizar todo esto de manera segura sería complejo. Utilizando sabiamente los canales, esta implementación de broker es bastante limpia y simple. Permítame no repetir el código, pero puede verificarlo en esta respuesta: Cómo transmitir un mensaje usando el canal. La implementación es perfectamente segura para uso concurrente, admite clientes “ilimitados” y no usa un solo mutex o variable compartida, solo canales.
Consulte también las preguntas relacionadas:
Leer valores de un hilo diferente
valoraciones y reseñas
No se te olvide compartir este ensayo si te fue de ayuda.