Resumen

Cuando varias gorutinas acceden al mismo valor de forma simultánea, pueden ocurrir errores silenciosos y difíciles de rastrear. Comprender este problema es fundamental para escribir programas concurrentes seguros, especialmente cuando se trabaja con operaciones sensibles como transacciones financieras.

¿Qué ocurre cuando las gorutinas comparten valores?

En programación concurrente existen dos escenarios principales. El primero es cuando cada gorutina es independiente y no comparte ningún dato con las demás. El segundo, más complejo, es cuando múltiples gorutinas necesitan leer y modificar el mismo valor [0:06]. Este segundo caso es donde aparecen los problemas.

Para ilustrarlo, se plantea un programa financiero con dos operaciones básicas sobre una misma cuenta bancaria: depositar y retirar. Cada operación es manejada por una gorutina diferente, pero todas trabajan sobre el mismo balance.

¿Cómo funcionan las operaciones de depósito y retiro?

La estructura del programa es sencilla [1:12]:

  • La función depositar toma el balance actual de la cuenta y le suma el valor depositado.
  • La función retirar primero verifica que el monto a retirar sea menor o igual al balance disponible, y luego resta esa cantidad.

En el ejemplo, el balance inicial es de quinientos. Se lanzan dos gorutinas: una deposita doscientos y otra deposita cien [1:38]. Ambas gorutinas leen el mismo valor de balance al iniciar.

¿Qué resultados pueden ocurrir con depósitos concurrentes?

Existen tres posibilidades [1:52]:

  • El depósito de cien se ejecuta primero y después el de doscientos.
  • El depósito de doscientos se ejecuta primero y luego el de cien.
  • Ambos depósitos ocurren al mismo tiempo.

En los dos primeros casos, el resultado es el esperado: un balance final de ochocientos. Pero en el tercer caso, ambas gorutinas leen el balance original de quinientos antes de que la otra escriba su resultado. Una termina con seiscientos y la otra con setecientos. El balance final será uno de esos dos valores, lo que significa que una transacción desaparece [2:43].

¿Qué es una condición de carrera?

Este problema se conoce como condición de carrera (race condition) [3:06]. Ocurre cuando dos o más gorutinas acceden a un recurso compartido sin ningún mecanismo de sincronización, y el resultado depende del orden impredecible en que se ejecutan.

Un segundo ejemplo refuerza el concepto [3:10]. Primero se deposita doscientos, lo que debería llevar el balance a setecientos. Luego se intenta retirar setecientos. La función de retiro verifica que el monto sea menor o igual al balance, pero si la gorutina de retiro lee el balance antes de que el depósito se refleje, verá solo quinientos y el retiro podría fallar o producir un resultado inconsistente [3:30].

Lo más peligroso de las condiciones de carrera es que son muy difíciles de detectar. El programa puede funcionar correctamente la mayoría de las veces y fallar solo bajo ciertas condiciones de tiempo, lo que complica su depuración.

¿Cómo resuelve Go las condiciones de carrera?

Go ofrece herramientas integradas para manejar estos riesgos. Una de ellas es el uso de locks (bloqueos), que permiten que solo una gorutina acceda a un recurso compartido a la vez [3:55]. Mientras una gorutina tiene el lock, las demás esperan hasta que se libere.

Este mecanismo garantiza que:

  • Las lecturas y escrituras sobre un valor compartido se realicen de forma atómica.
  • No se pierdan transacciones ni se generen estados inconsistentes.
  • El programa mantenga la integridad de los datos incluso bajo alta concurrencia.

Identificar y prevenir condiciones de carrera es una habilidad esencial en el desarrollo con Go. Si has trabajado con programas concurrentes, comparte tu experiencia con estos problemas y cómo los has resuelto.