Bienvenidos sean a este post, hoy hablaremos sobre dos temas muy particulares para entender a los locks.
A la hora de escribir codigo multithreading necesitas estar atento a los peligros de cuando no lo ejecutamos de forma lineal, esto implica que este tipo de codigo esta expuesto al riesgo de ser pausado por el planificador en cualquier momento porque decidio darle mas tiempo de CPU a otro flujo de informacion, por lo tanto esta conducta nos expone a dos tipos de riesgo como las condiciones de carrera (race conditions) y deadlocks, hablemos sobre el primero.
Race conditions
Una condicion de carrera (por su traduccion) es una conducta del sistema cuando la salida de un proceso depende en la secuencia o el tiempo de otros eventos incontrolables, cuandos estos eventos no se desarrollan en el orden previsto por el programador la condicion se convierte en un bug (error), para entenderlo mejor analicemos un ejemplo generico.
Imaginemos que tenemos dos threads corriendo y los dos ejecutan la misma tarea, la cual consiste en leer un dato desde una ubicacion, ejecuta una accion en base al valor obtenido, incrementamos el valor en una unidad y lo volvemos a guardar y todo esto es para pasar el valor a un API, analicemos el primer escenario.
Escenario A – la race condition no ocurre
El thread A lee el valor (1), pasa 1 al API, luego lo incrementa en 2, lo vuelve a guardar, despues de esto el planificador pausa al thread A y ejecuta al thread B, lee nuevamente el valor (ahora 2), pasa 2 al API, lo incrementa a 3 y lo vuelve a guardar, despues que la operacion se ha ejecutado dos veces el valor almacenado es correcto:
1 + 2 = 3
Adema el API ha sido llamado correctamente con ambos 1 y 2, pasemos al siguiente escenario.
Escenario B – la race condition ocurre
El thread A lee el valor (1), lo pasa al API, lo incrementa en 2 pero antes de guardar el nuevo valor el planificador pausa al thread A para favorecer al thread B, comienza a correr el thread B y lee el valor (aun es 1), lo pasa al API, lo incrementa al valor 2 y lo guarda nuevamente, ahora nuestro planificador vuelve al thread A y lo retoma donde lo habia pausado y este ejecutara la accion pendiente que era salvar nuevamente el valor que sigue siendo 2, despues de este escenario por mas que se haya ejecutado la operacion dos veces como en el escenario A el valor almacenado es 2 y la API se llamo dos veces con el valor 1.
En la vida real con multiples threads y con codigo real ejecutando varias operaciones el comportamiento general explota en una miriada de posibilidades, como pueden imaginarse el principal problema es que hace a nuestro codigo no determinista, lo cual es malo, si bien en informatica existen areas donde no determinista es usado para lograr ciertas cosas, y esta bien, pero por lo general necesitamos saber como nuestro codigo se comportara y race conditions nos imposibilita hacer eso.
Una posibilidad que nos ayuda con la race condition son los llamados locks, para solucionar el ejemplo anterior todo lo que necesitamos es un lock alrededor del proceso, consideremos al lock como un guardian que solo permitira un thread para agarrarlo (usualmente se dice adquirir un lock) y hasta que ese lock no suelta ese thread ningun otro thread puede adquirirlo, por lo tanto tendran que esperar hasta que el lock este disponible de nuevo.
Escenario C – usando un lock
El thread A adquiere un lock, lee el valor (1), lo pasa al API, lo incrementa a 2, pero el planificador lo suspende, se le da tiempo de CPU al thread B y trata de adquirir el lock pero este todavia no fue liberado por thread A por lo tanto thread B se sienta a esperar, el planificador observa esto y rapidamente vuelve al thread A, este guarda el valor 2 y libera el lock, ahora haciendo lo posible para los otros threads.
En este punto si el lock es adquirido por thread A o thread B (porque el planificador puede haber decidido cambiarlo de nuevo) no es importante, el procedimiento siempre se ejecutara correctamente porque el lock asegura que el thread lee el valor, tiene que completar el proceso (pasa API, incrementar y guardar) antes que cualquier otro thread pueda leer el valor, disponemos de multitud de locks en las librerias estandar, a continuacion hablaremos sobre uno de ellos.
Deadlocks
Este es un estado en el cual cada miembro de este grupo esta esperando por otro miembro para tomar accion, tal como enviar un mensaje o, mas comunmente, liberando un lock o un recurso, vamos a imaginar un ejemplo para entender el concepto, tenemos a dos niños que juegan juntos, encontramos un juguete de dos partes y le damos una parte a cada uno, y ellos querran que el otro suelte la parte que tienen, asi que ninguno de ellos podra jugar con el juguete mientras cada uno tenga la mitad del mismo, y esperan indefinidamente que el otro suelte la mitad que tiene.
Otro ejemplo que podriamos hacer son dos threads que ejecutan procedimientos iguales, los procedimientos requieren adquirir dos recursos, A y B, ambos guardados por locks separados, supongamos que el thread 1 adquiere a A y el thread 2 adquiere a B, esto ocasiona que ambos esperen indefinidamente hasta que el otro suelte el recurso que tiene pero esto no ocurrira porque ambos fueron instruidos para esperar y adquirir el segundo recurso en orden para completar la secuencia, como ven los threads pueden ser mas tercos que los niños
Esto podemos resolverlo de varias formas, la mas simple es establecer un orden de adquisicion de recursos, lo cual significa que el thread que obtiene a A, tambien obtiene a B, C, etc, otra forma es poner un lock a todo el proceso de adquisicion de recursos, incluso si algo ocurre fuera del orden seguira estando dentro del contexto del lock, lo cual hace que un solo thread pueda adquirir todos los recusos a la vez.
En resumen, hoy hemos visto que es un race condition, como nos beneficia pero tambien como nos puede perjudicar, despues hemos visto como solucionar por medio de un lock la situacion anterior pero tambien hemos visto como el uso de lock puede derivar en un deadlock, generando otro inconveniente muy popular, espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.50
