Cursada
Clase 11
Siguiendo las disposiciones sanitarias del país, esta clase fue de manera virtual. Presentamos el TP 2 y vimos una pequeña introducción a promises.
Pueden volver a ver la clase entrando acá.
Ejercicios para trabajar en clase
Clase 10
Interactuamos con una API REST.
Ejercicios para trabajar en clase
Usando una API REST
Apuntes
Clase 9
Seguimos con callbacks en NodeJS.
Ejercicios para trabajar en clase
Clase 8
¡Arrancamos con NodeJS!
Ejercicios para trabajar en clase
Primeros pasos en NodeJS
Apuntes
Clase 7
Vimos monitores en Python. La idea básica de un monitor es que funciona como un Lock
pero que además puede esperar a una determinada condición.
monitor = threading.Condition()
# Consumir un ítem
with monitor: # hace el acquire (y al final el release)
while not hay_un_item():
monitor.wait() # espera hasta que se dé la señal
consumir_un_item()
# Producir un ítem
with monitor:
hacer_un_item()
monitor.notify() # notifica la señal
# el with es una forma fácil de hacer acquire y release.
# Hubiera sido lo mismo hacer así:
monitor.acquire()
try:
hacer_un_item()
monitor.notify()
finally:
monitor.releas()
El Manejo básico de monitores es el siguiente
monitor = threading.Condition() #crea el monitor. Se le puede pasar como parámetro un Lock en particular
monitor.acquire() # mismo que para semáforos
monitor.release() # mismo que para semáforos
monitor.wait() # esperar hasta recibir señal
monitor.notify() # dar señal a algún thread que está esperando
monitor.notifyAll() # dar señal a todos los threads que están esperando
Se puede leer la documentación desde acá python-threading-condition-objects.
Ejercicios para trabajar en clase
Clase 6
Ejercicios para trabajar en clase
Clase 5
Hablamos brevemente sobre dos problemas inherentes al manejo de concurrencia con threds: starvation y deadlock.
Sobre el segundo, vimos cómo ocurría fácilmente al intentar resolver el problema de la cena de los filósofos y que una posible solución en Python es usar timeouts (ver ejercicio resuelto).
Ejercicios para trabajar en clase
Filósofos comensales (ejemplo de deadlock)
Clase 4
Repasamos dos ideas importantes a tener en cuenta en concurrencia:
- El (potencial) indeterminismo en la ejecución de un programa con threads.
- Terminado el main thread, pueden quedar otros threads ejecutándose (e incluso cambiando cosas mostradas por el main thread).
En comensales.py
nos topamos con otra idea importante a la hora de manejar threads: el orden en que se desbloquean es aleatorio.
Se puede leer de acá, del acquire()
.
Todo esto en el contexto de seguir practicando con semáforos como herramienta de sincronización entre threads.
Ejercicios para trabajar en clase
Clase 3
Trabajamos con la primitiva de sincronización más antigua, creada durante la década de 1960 por Edsger_Dijkstra: los semáforos.
Esta primitiva, a diferencia del mutex (o lock) que veníamos usando, permite especificar su valor inicial. Internamente lleva un contador que se decrementa con cada acquire()
y se incrementa con cada release()
, bloqueando al thread cuando el contador llega a cero.
Por ejemplo:
import threading
# Inicialmente el contador vale 2.
sem = threading.Semaphore(2)
# El contador pasa a valer 1. No se bloquea el thread.
sem.acquire()
# El contador pasa a valer 0. No se bloquea el thread.
sem.acquire()
# Se bloquea el thread.
# Se liberará cuando algún otro thread ejecute release() sobre este mismo semáforo.
sem.acquire()
Vimos que podemos pensar al lock como un caso particular del semáforo, donde su valor inicial es 1 (también se lo llama semáforo binario).
Reescribimos el código del ejercicio de las operaciones no conmutativas usando semáforos y luego seguimos ejercitando con algunas simulaciones.
Ejercicios para trabajar en clase
Clase 2
Primero repasamos algunas cosas:
- Cómo importar bibliotecas, y cómo importar clases o funciones que definimos en otro archivo.
- Cómo crear y lanzar threads tanto desde una función como desde una clase.
Después vimos como crear/lanzar/esperar muchos threads:
- Con un
for
creamos y lanzamos muchos threads. - Después guardándonos todos los threads lanzados en una lista, la usamos para esperarlos a todos (mediante otro
for
) - Vimos que guardar los threads en una lista permite, por ejemplo, luego esperarlos a todos. Sino los creamos y lanzamos, pero no los podemos controlar.
- También vimos que no es lo mismo lanzar y esperar, lanzar y esperar, etc., que lanzar todos y luego esperarlos a todos. Los tiempos de ejecución son muy distintos (¡y la ejecución en sí también!).
- Nos valimos de la traza (línea de tiempo de ejecución) para entender qué estaba pasando en cada caso.
Por último definimos dos funciones que operaban sobre la misma variable, pero cuyas operaciones no eran conmutativas (es decir importa el orden en que se aplican):
- Nuevamente surgió el tema de la indeterminación al lanzar sendos threads de cada función.
- Vimos cómo arreglar la indeterminación desde el main thread mediante
join()
. - Pero vimos que esta solución no está tan buena porque paraliza al main thread.
- Luego vimos otra solución usando
locks
que hizo que los threads se esperen (o sincronicen) entre sí, independientemente de lo que siga haciendo el main thread (es decir, no necesitamos paralizarlo). - Nuevamente nos valimos de la traza para entender lo que estaba pasando.
Ejercicios para trabajar en clase
Primeros pasos en Python (recargado)
Apuntes
Python importando
Clase 1
Recuperamos conceptos previos sobre programación concurrente: salieron palabras como hilos, procesos, recursos y algunas otras. En resumen, nos quedó esto en el pizarrón:
Vimos las condiciones de la cursada y empezamos a jugar un poco con Python:
- creamos hilos con la biblioteca
threading
y vimos que era necesario ejecutarstart()
para que comiencen; - nos dimos cuenta de que el thread principal (o main thread) no espera a los hilos automáticamente;
- ensayamos una sincronización precaria usando
time.sleep
y calculando cuánto tardaba cada hilo en ejecutar; - por último, aprendimos que podemos “quedarnos esperando” a que un thread termine, enviándole el mensaje
join()
.