La programación concurrente se basa en el uso de hilos. Cuando tenemos varios hilos compartiendo recursos entre sí se pueden presentar algunos problemas, entre ellos:
Carreras (race conditions)
Los race conditions ocurren cuando dos o más hilos desean acceder a un recurso lo más pronto posible. Por ejemplo, tenemos una variable C inicializada en 0, pero después en alguna parte del código a C se le asigna el valor de C + 1. Entonces un hilo llegará primero y al ver que vale 0, le agregará el valor de 1, almacena ese dato y ahora C vale 1.
Pero si otro hilo llega después, pensará que C vale 1, entonces añadirá otro 1, almacenará ese dato y entonces C vale 2. Esto puede ser un problema en un sistema automatizado de dosificación de medicamentos. Sí el sistema es concurrente, ¿Entonces que hará? ¿Le dará una dosis o dos dosis? ¿Cómo se resuelve?.
Puntos muertos (deadlock)
Un deadlock ocurre cuando un hilo espera por un evento que nunca sucederá. Para que suceda un deadlock deben de cumplirse 4 condiciones:
- Los hilos deben tener acceso exclusivo a los recursos.
- Los hilos deben contener algunos recursos mientras esperan otros.
- Los recursos no se pueden eliminar de los hilos en espera.
- Existe una cadena circular de hilos en las que cada uno contiene uno o más recursos del siguiente hilo.
Problema de los filósofos
El problema de los filósofos plantea que hay una mesa redonda con 5 filósofos conversando. Frente a ellos hay un plato de comida, y hay un palillo chino a la derecha y otro a la izquierda (5 palillos en total). Para comer se necesitan dos palillos chinos, eso significa que no todos podrán comer al mismo tiempo.
Hay muchas formas de abordar el problema, pero esas soluciones pueden dar otros problemas. Podría ser un sistema de turnos, pero entonces algunos se quedarían esperando hasta que la comida esté fría.
Estrategias para evitar los deadlocks
Dos estrategias para evitar que se cumpla alguna de las condiciones que producen deadlocks son el uso de semáforos o monitores.
- Semáforos: Funcionarían como una variable de tipo entero, asociado a un mecanismo de cola de hilos. Si el semáforo toma valores de “0” y “1”, es binario. En caso contrario, es un “semáforo contador”.
- Monitores: Estructuras de datos abstractas basados en los monitores o kernel de los S.O. Los monitores tienen 4 componentes principales:
- Inicialización: contiene código a ser ejecutado.
- Datos privados: procedimientos que se utilizan desde dentro del monitor.
- Métodos del monitor: procedimientos que se pueden llamar desde fuera.
- Cola de entrada: hilos que llaman a algún método del monitor, pero no tienen permiso para ejecutarse aún.
Algunos lenguajes que implementan concurrencia
Estos lenguajes pueden hacer uso de la concurrencia, aunque de forma distinta entre ellos.
- JavaScript
- C#
- Golang
- Rust
- Elixir
- Haskell
Conclusión
La programación concurrente nos ayuda a ejecutar procedimientos de manera más eficiente en algunos casos, al no ejecutarlos de manera secuencial. Sin embargo, la programación concurrente trae una serie de problemas como las race conditions y deadlocks, para los cuales hay distintas estrategias que ayudan a evitarlos.
Contribución creada por: Ciro Villafraz con los aportes de Valentina Barrios.
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?