SlideShare a Scribd company logo
Python
Asíncrono
¿echas de menos las
promesas?
@javierabadia
DISCLAIMER
Python Asíncrono - Async Python
pero…
Python Asíncrono - Async Python
Python Asíncrono - Async Python
Javier Abadía
VP of Engineering @ StyleSage
Python Asíncrono - Async Python
tuplas, listas, etc
comprensiones
librerías para todo
claves de diccionarios
funciones anónimas
asincronía
❤
const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`;
// list of cities
const CITIES = [
'London', 'Tokyo', 'Melbourne', 'Vancouver',
'Lagos', 'Berlin', 'Paris', 'Johannesburg',
'Chicago', 'Mumbai', 'Cairo', 'Beijing'
];
const fetchTemperatureForCity = city => {
// For example: ['Lagos', 29.28]
return fetch(`${API_URL}&q=${encodeURIComponent(city)}`)
.then(response => response.json())
.then(data => [ city, data.main.temp || null ]);
}
const fetchTemperatureForCities = cities => {
return Promise.all(cities.map(fetchTemperatureForCity))
.then(temps => {
return temps.reduce((data, [ city, temp ]) => {
return { ...data, [city]: Number.isFinite(temp) ? temp.toFixed(2) * 1 : null };
}, {});
});
}
fetchTemperatureForCities(CITIES)
.then(console.log, console.error);
si tuviera que
hacer esto en una
vista de Django…
…no sabría
Si Python fuera un bar…
BAR
BAR
Una Hamburguesa,
con patatas grandes
y una cerveza,
por favor Enseguida!
esperar esperar servir la cerveza
pedir la hamburguesa
a la cocina
poner las patatatas
a freir
sacar las patatas
de la freidora
1 cliente = 10 min
BAR
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
3 clientes (10, 20, 30) ⋍ 20 min
atender más
clientes a la vez
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
atender a un cliente
en menos tiempo
hacer un mejor
uso de los recursos
Python Asíncrono - Async Python
https://gist.github.com/hellerbarde/2843375
Tiempos
System Event Actual Latency Scaled Latency
One CPU cycle 0.4 ns 1 s
Level 1 cache access 0.9 ns 2 s
Level 2 cache access 2.8 ns 7 s
Level 3 cache access 28 ns 1 min
Main memory access (DDR DIMM) ~100 ns 4 min
NVMe SSD I/O ~25 μs 17 hrs
SSD I/O 50–150 μs 1.5 - 4 days
Rotational disk I/O 1–10 ms 1-9 months
Internet call: San Francisco to New York
City
65 ms 5 years
Internet call: San Francisco to Hong Kong 141 ms 11 years
url = 'http://example.com/david-hasselhoff.jpg'
path = 'media/cool-pics/screen-background.jpg’
r = requests.get(url, stream=True)
if r.status_code == 200:
with open(path, 'wb') as f:
for chunk in r.iter_content(1024):
f.write(chunk)
im = Image.open(path)
im.thumbnail(size, Image.ANTIALIAS)
im.save(thumbnail_path, "JPEG")
pic = CoolPics(img=path, thumbnail=thumbnail_path)
pic.save()
varios años
varios meses
varios meses
varios meses
varios meses
unas pocas horas
varios meses
< de una hora
varios meses
url = 'http://example.com/david-hasselhoff.jpg'
path = 'media/cool-pics/screen-background.jpg’
r = requests.get(url, stream=True)
if r.status_code == 200:
with open(path, 'wb') as f:
for chunk in r.iter_content(1024):
f.write(chunk)
im = Image.open(path)
im.thumbnail(size, Image.ANTIALIAS)
im.save(thumbnail_path, "JPEG")
pic = CoolPics(img=path, thumbnail=thumbnail_path)
pic.save()
varios años
varios meses
varios meses
varios meses
varios meses
unas pocas horas
varios meses
< de una hora
varios meses
naturalezasecuencial
x 2000
¿qué podemos hacer?
BAR
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
3 clientes (10, 12, 20) ⋍ 14 min
import threading
import time
class MyThread(threading.Thread):
def __init__(self, thread_id, name, delay):
super().__init__()
self.thread_id = thread_id
self.name = name
self.delay = delay
def run(self):
print("Starting " + self.name)
# Get lock to synchronize threads
# g_thread_lock.acquire()
print_time(self.name, 3, self.delay)
# Free lock to release next thread
# g_thread_lock.release()
def print_time(thread_name, counter, delay):
while counter:
time.sleep(delay)
print("%s: %s" % (
thread_name,
time.ctime(time.time()))
)
counter -= 1
# …
# …
g_thread_lock = threading.Lock()
threads = []
# Create new threads
thread1 = MyThread(1, "Thread-1", 1)
thread2 = MyThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
# Add threads to thread list
threads.append(thread1)
threads.append(thread2)
# Wait for all threads to complete
for t in threads:
t.join()
print("Exiting Main Thread")
threading
import threading
import time
class MyThread(threading.Thread):
def __init__(self, thread_id, name, delay):
super().__init__()
self.thread_id = thread_id
self.name = name
self.delay = delay
def run(self):
print("Starting " + self.name)
# Get lock to synchronize threads
# g_thread_lock.acquire()
print_time(self.name, 3, self.delay)
# Free lock to release next thread
# g_thread_lock.release()
def print_time(thread_name, counter, delay):
while counter:
time.sleep(delay)
print("%s: %s" % (
thread_name,
time.ctime(time.time()))
)
counter -= 1
# …
# …
g_thread_lock = threading.Lock()
threads = []
# Create new threads
thread1 = MyThread(1, "Thread-1", 1)
thread2 = MyThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
# Add threads to thread list
threads.append(thread1)
threads.append(thread2)
# Wait for all threads to complete
for t in threads:
t.join()
print("Exiting Main Thread")
crear las threads
import threading
import time
class MyThread(threading.Thread):
def __init__(self, thread_id, name, delay):
super().__init__()
self.thread_id = thread_id
self.name = name
self.delay = delay
def run(self):
print("Starting " + self.name)
# Get lock to synchronize threads
# g_thread_lock.acquire()
print_time(self.name, 3, self.delay)
# Free lock to release next thread
# g_thread_lock.release()
def print_time(thread_name, counter, delay):
while counter:
time.sleep(delay)
print("%s: %s" % (
thread_name,
time.ctime(time.time()))
)
counter -= 1
# …
# …
g_thread_lock = threading.Lock()
threads = []
# Create new threads
thread1 = MyThread(1, "Thread-1", 1)
thread2 = MyThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
# Add threads to thread list
threads.append(thread1)
threads.append(thread2)
# Wait for all threads to complete
for t in threads:
t.join()
print("Exiting Main Thread")
arrancarlas
import threading
import time
class MyThread(threading.Thread):
def __init__(self, thread_id, name, delay):
super().__init__()
self.thread_id = thread_id
self.name = name
self.delay = delay
def run(self):
print("Starting " + self.name)
# Get lock to synchronize threads
# g_thread_lock.acquire()
print_time(self.name, 3, self.delay)
# Free lock to release next thread
# g_thread_lock.release()
def print_time(thread_name, counter, delay):
while counter:
time.sleep(delay)
print("%s: %s" % (
thread_name,
time.ctime(time.time()))
)
counter -= 1
# …
# …
g_thread_lock = threading.Lock()
threads = []
# Create new threads
thread1 = MyThread(1, "Thread-1", 1)
thread2 = MyThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
# Add threads to thread list
threads.append(thread1)
threads.append(thread2)
# Wait for all threads to complete
for t in threads:
t.join()
print("Exiting Main Thread")
¿sincronizarlas?
import threading
import time
class MyThread(threading.Thread):
def __init__(self, thread_id, name, delay):
super().__init__()
self.thread_id = thread_id
self.name = name
self.delay = delay
def run(self):
print("Starting " + self.name)
# Get lock to synchronize threads
# g_thread_lock.acquire()
print_time(self.name, 3, self.delay)
# Free lock to release next thread
# g_thread_lock.release()
def print_time(thread_name, counter, delay):
while counter:
time.sleep(delay)
print("%s: %s" % (
thread_name,
time.ctime(time.time()))
)
counter -= 1
# …
# …
g_thread_lock = threading.Lock()
threads = []
# Create new threads
thread1 = MyThread(1, "Thread-1", 1)
thread2 = MyThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
# Add threads to thread list
threads.append(thread1)
threads.append(thread2)
# Wait for all threads to complete
for t in threads:
t.join()
print("Exiting Main Thread")
esperar a que terminen
thread safety
hcp://edot.org/pyfaq/what-kinds-of-global-value-mutafon-are-thread-safe.htm
songs = []
songs.append(my_song)
songs.pop()
songs.sort()
artist_ages.keys()
i = i+1
songs[i] = songs[j]
if songs:
song = songs.pop()
artist_ages[‘Bob’] += 1
Python Asíncrono - Async Python
hay mejores
formas de
usar threads
BAR BAR
BAR BAR
BAR BAR
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
esperar
poner las
patatatas a freir
pedir la
hamburguesa a la
cocina
esperar servir la cerveza
sacar las patatas
de la freidora
3 clientes (10, 10, 20) ⋍ 13.3 min
from multiprocessing import Lock, Process,
Queue, current_process
import time
import queue # imported for using queue.Empty exception
def do_job(tasks_to_accomplish, tasks_that_are_done):
while True:
try:
task = tasks_to_accomplish.get_nowait()
except queue.Empty:
break
else:
print(task)
tasks_that_are_done.put(task +
' is done by ' + current_process().name)
time.sleep(.5)
return True
def main():
number_of_task = 10
number_of_processes = 4
tasks_to_accomplish = Queue()
tasks_that_are_done = Queue()
processes = []
for i in range(number_of_task):
tasks_to_accomplish.put("Task no " + str(i))
# creating processes
for w in range(number_of_processes):
p = Process(
target=do_job,
args=(tasks_to_accomplish, tasks_that_are_done)
)
processes.append(p)
p.start()
# completing process
for p in processes:
p.join()
# print the output
while not tasks_that_are_done.empty():
print(tasks_that_are_done.get())
return True
if __name__ == '__main__':
main()
mulf-processing
from multiprocessing import Lock, Process,
Queue, current_process
import time
import queue # imported for using queue.Empty exception
def do_job(tasks_to_accomplish, tasks_that_are_done):
while True:
try:
task = tasks_to_accomplish.get_nowait()
except queue.Empty:
break
else:
print(task)
tasks_that_are_done.put(task +
' is done by ' + current_process().name)
time.sleep(.5)
return True
def main():
number_of_task = 10
number_of_processes = 4
tasks_to_accomplish = Queue()
tasks_that_are_done = Queue()
processes = []
for i in range(number_of_task):
tasks_to_accomplish.put("Task no " + str(i))
# creating processes
for w in range(number_of_processes):
p = Process(
target=do_job,
args=(tasks_to_accomplish, tasks_that_are_done)
)
processes.append(p)
p.start()
# completing process
for p in processes:
p.join()
# print the output
while not tasks_that_are_done.empty():
print(tasks_that_are_done.get())
return True
if __name__ == '__main__':
main()
multi-processing
from multiprocessing import Lock, Process,
Queue, current_process
import time
import queue # imported for using queue.Empty exception
def do_job(tasks_to_accomplish, tasks_that_are_done):
while True:
try:
task = tasks_to_accomplish.get_nowait()
except queue.Empty:
break
else:
print(task)
tasks_that_are_done.put(task +
' is done by ' + current_process().name)
time.sleep(.5)
return True
def main():
number_of_task = 10
number_of_processes = 4
tasks_to_accomplish = Queue()
tasks_that_are_done = Queue()
processes = []
for i in range(number_of_task):
tasks_to_accomplish.put("Task no " + str(i))
# creating processes
for w in range(number_of_processes):
p = Process(
target=do_job,
args=(tasks_to_accomplish, tasks_that_are_done)
)
processes.append(p)
p.start()
# completing process
for p in processes:
p.join()
# print the output
while not tasks_that_are_done.empty():
print(tasks_that_are_done.get())
return True
if __name__ == '__main__':
main()
multi-processing
crear colas IPC
from multiprocessing import Lock, Process,
Queue, current_process
import time
import queue # imported for using queue.Empty exception
def do_job(tasks_to_accomplish, tasks_that_are_done):
while True:
try:
task = tasks_to_accomplish.get_nowait()
except queue.Empty:
break
else:
print(task)
tasks_that_are_done.put(task +
' is done by ' + current_process().name)
time.sleep(.5)
return True
def main():
number_of_task = 10
number_of_processes = 4
tasks_to_accomplish = Queue()
tasks_that_are_done = Queue()
processes = []
for i in range(number_of_task):
tasks_to_accomplish.put("Task no " + str(i))
# creating processes
for w in range(number_of_processes):
p = Process(
target=do_job,
args=(tasks_to_accomplish, tasks_that_are_done)
)
processes.append(p)
p.start()
# completing process
for p in processes:
p.join()
# print the output
while not tasks_that_are_done.empty():
print(tasks_that_are_done.get())
return True
if __name__ == '__main__':
main()
multi-processing
crear los procesos
from multiprocessing import Lock, Process,
Queue, current_process
import time
import queue # imported for using queue.Empty exception
def do_job(tasks_to_accomplish, tasks_that_are_done):
while True:
try:
task = tasks_to_accomplish.get_nowait()
except queue.Empty:
break
else:
print(task)
tasks_that_are_done.put(task +
' is done by ' + current_process().name)
time.sleep(.5)
return True
def main():
number_of_task = 10
number_of_processes = 4
tasks_to_accomplish = Queue()
tasks_that_are_done = Queue()
processes = []
for i in range(number_of_task):
tasks_to_accomplish.put("Task no " + str(i))
# creating processes
for w in range(number_of_processes):
p = Process(
target=do_job,
args=(tasks_to_accomplish, tasks_that_are_done)
)
processes.append(p)
p.start()
# completing process
for p in processes:
p.join()
# print the output
while not tasks_that_are_done.empty():
print(tasks_that_are_done.get())
return True
if __name__ == '__main__':
main()
multi-processing
ejecutar los procesos
from multiprocessing import Lock, Process,
Queue, current_process
import time
import queue # imported for using queue.Empty exception
def do_job(tasks_to_accomplish, tasks_that_are_done):
while True:
try:
task = tasks_to_accomplish.get_nowait()
except queue.Empty:
break
else:
print(task)
tasks_that_are_done.put(task +
' is done by ' + current_process().name)
time.sleep(.5)
return True
def main():
number_of_task = 10
number_of_processes = 4
tasks_to_accomplish = Queue()
tasks_that_are_done = Queue()
processes = []
for i in range(number_of_task):
tasks_to_accomplish.put("Task no " + str(i))
# creating processes
for w in range(number_of_processes):
p = Process(
target=do_job,
args=(tasks_to_accomplish, tasks_that_are_done)
)
processes.append(p)
p.start()
# completing process
for p in processes:
p.join()
# print the output
while not tasks_that_are_done.empty():
print(tasks_that_are_done.get())
return True
if __name__ == '__main__':
main()
multi-processing
esperar a que
terminen todos
I/O bound CPU bound
multi-
threading
mulf-
processing
comparten
memoria
GIL
más pesados
no comparten
memoria
IPC
Global Interpreter Lock
• El intérprete de Python no es thread-safe
• Específico de ALGUNAS implementaciones de Python (CPython, PyPy)
• Solo una thread puede estar interpretando código Python en un
proceso
• El intérprete “suelta” el “lock” para operaciones I/O (o incluso NumPy)
• Consecuencias:
• tareas limitadas por I/O van más rápido con múltiples threads
• tareas limitadas por CPU van más lento con múltiples threads
más rápido
¡más lento!
(por culpa del GIL)
más rápido
(pero más ”caro”)
más rápido
I/O bound CPU bound
multi-
threading
multi-
processing
comparten
memoria
GIL
más pesados
no comparten
memoria
IPC
pero… ¿cómo lo hace JavaScript?
BAR
servir la cerveza
pedir la hamburguesa a la
cocina
poner las patatatas a
freir
sacar las patatas de
la freidora
servir la cerveza
pedir la hamburguesa a la
cocina
poner las patatatas a
freir
sacar las patatas de
la freidora
servir la cerveza
pedir la hamburguesa a la
cocina
poner las patatatas a
freir
sacar las patatas de
la freidora
3 clientes (4,5,7) ⋍ 5,33 min
Modelo de Concurrencia de JavaScript
• en JavaScript solo hay una thread de usuario
• el código se ejecuta “por turnos”, sin ninguna interrupción, siempre hasta
el final
• dentro de un “bucle de eventos”
• TODAS las operaciones de I/O son asíncronas:
• se lanzan en el momento
• el código no espera a que se termine la ejecución
• el resultado se recibe en un callback (o una Promesa) que se ejecutará en un turno
en el futuro
• También son asíncronas todas las funciones que llaman a una función
asíncrona
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
Python Asíncrono - Async Python
Python Asíncrono - Async Python
TURNO 1 TURNO 2
event loopevent loopevent loopevent loop
TURNO 1 TURNO 2
Python asyncio - async/await
Python 3.4+
# https://docs.python.org/3/library/asyncio-task.html
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
bucle de eventos
await ≈ yield
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
t1 = say_after(1, 'hello')
t2 = say_after(2, 'world')
await asyncio.gather(t1, t2)
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
concurrencia cooperativa
¡por fin!
¿no?
si no hay cooperación, no funciona
• en JavaScript la cooperación es
obligatoria
• todas las librerías son asíncronas
• en Python casi todas las librerías
son síncronas (no cooperan)
nuestro código no debe bloquearse
debe devolver el control al bucle de eventos
no hay un “scheduler pre-emptivo”
el problema es…
• todo lo que NO podemos usar
• http, requests
• acceso a bases de datos
• I/O de ficheros
• necesitamos nuevas librerías para todo
• aiohttp
• aiopg
• aiomysql
• aiofiles
• https://github.com/timofurrer/awesome-asyncio
SYNC ASYNC
es difícil
• converfr un proyecto existente en async
• o una parte
• en un nuevo proyecto async
• encontrar y usar librerías
• p.ej script que se conecta a nuestra cuenta de GitHub y crea un informe de
branches en Excel
• pygithub - hace llamadas de red
• openpyxl - lee y escribe ficheros
Python Asíncrono - Async Python
Python Asíncrono - Async Python
¿entonces?
concurrent.futures
• librería de alto nivel
• usando multi-processing o multi-threading con la misma interfaz
• proporciona una sintaxis parecida a las promesas
• compatible con todas las librerías síncronas
• si son thread-safe podemos usar multi-threading
• si no, tendremos que usar multi-processing
Python 3.22011-02-20
import threading
from concurrent import futures
from random import random
from time import sleep
import requests
nums = range(1, 11)
url_tpl = "http://jsonplaceholder.typicode.com/todos/{}"
def get_data(myid):
url = url_tpl.format(myid)
data = requests.get(url).json()
sleep(random() * 5)
return data
def main():
with futures.ThreadPoolExecutor(4) as executor:
results = executor.map(get_data, nums)
print()
for result in results:
print(result)
if __name__ == '__main__':
main()
futures
import threading
from concurrent import futures
from random import random
from time import sleep
import requests
nums = range(1, 11)
url_tpl = "http://jsonplaceholder.typicode.com/todos/{}"
def get_data(myid):
url = url_tpl.format(myid)
data = requests.get(url).json()
sleep(random() * 5)
return data
def main():
with futures.ThreadPoolExecutor(4) as executor:
results = executor.map(get_data, nums)
print()
for result in results:
print(result)
if __name__ == '__main__':
main()
futures
ejecutar en 4 threads
import threading
from concurrent import futures
from random import random
from time import sleep
import requests
nums = range(1, 11)
url_tpl = "http://jsonplaceholder.typicode.com/todos/{}"
def get_data(myid):
url = url_tpl.format(myid)
data = requests.get(url).json()
sleep(random() * 5)
return data
def main():
with futures.ThreadPoolExecutor(4) as executor:
results = executor.map(get_data, nums)
print()
for result in results:
print(result)
if __name__ == '__main__':
main()
futures
¡librerías síncronas!
def main():
# with futures.ProcessPoolExecutor(4) as executor:
with futures.ThreadPoolExecutor(4) as executor:
jobs = [executor.submit(get_data, num) for num in nums]
for comp_job in futures.as_completed(jobs):
print(comp_job.result())
futures
resultados según están
disponibles
def main():
with futures.ThreadPoolExecutor(5) as executor:
jobs = [executor.submit(get_img, num) for num in range(20)]
for comp_job in futures.as_completed(jobs):
img_path = comp_job.result()
executor.submit(add_meme_to_img, img_path)
futures
pasos encadenados
def main():
# with futures.ProcessPoolExecutor(4) as executor:
with futures.ThreadPoolExecutor(4) as executor:
jobs = [executor.submit(get_data, num) for num in nums]
done, not_done = futures.wait(jobs, return_when=futures.FIRST_COMPLETED)
print(done.pop().result())
print(len(not_done))
for not_done_job in not_done:
not_done_job.cancel()
print()
print('finished')
futures
“carreras” de futuros
¿y lo de la thread-safety?
• evitar los efectos secundarios o variables compartidas
• funciones puras - programación funcional
• cuidado con las librerías que usamos, pueden no ser thread-safe
Opciones
• Multithreading
• Multiprocessing
• Cooperative concurrency Async/Await
• concurrent.futures
• Tornado/Twisted
• Curio/Trio
• Django 3.x ?
¿y qué pasa con Django?
vistas con múlfples
llamadas I/O
(disco/red/bd)
vistas con múlfples
llamadas I/O
(disco/red/bd)
Asincronía en Django
tareas de larga
duración
fuera del ciclo de
request/response
websockets con
muchos clientes
¡ESTO!
async/await
celery
Django 3.0
async!
todavía no
https://www.aeracode.org/2018/06/04/django-async-roadmap/
Request path
• WSGI - ASGI handler
• URL routing
• Middlewares
• Views
ORM
Templates
Forms
Caching
Sessions
Authentication
Admin
Email
Static files
Signals
DjangoCon 2019 - Just Add Await: Retrofitting Async Into Django by Andrew Godwin https://www.youtube.com/watch?v=d9BAUBEyFgM
Async Django
• https://www.aeracode.org/2018/06/04/django-async-roadmap/
Timeline
• Python 3.2 (2011-02-20)
• concurrent.futures
• Python 3.4 (2014-03-16)
• asyncio provisional
• Python 3.5 (2015-09-13)
• PEP 492, coroufnes with async and await syntax.
• Python 3.6 (2016-12-23)
• PEP 525: Asynchronous Generators
• PEP 530: Asynchronous Comprehensions
• asyncio stable
• Python 3.7 (2018-06-27)
• asyncio faceli|
• Python 3.8 (2019-10-14)
• async REPL
• Twisted (2003)
• Netty (2004)
• Java
• Node.js (2009)
• JavaScript
• non-blocking by default
• npm
• Tornado (2010)
resumiendo
• el mundo es asíncrono
• muchas aplicaciones son I/O bound
• podemos usar mejor los recursos:
• multi-proceso,
• multi-thread
• concurrencia cooperativa
• cada tarea se puede completar en menos tiempo
• paralelizar acciones necesarias para completar una tarea (a veces)
• con los mismos recursos puedo ejecutar más tareas
• paralelizar acciones necesarias para múltiples tareas
¡muchas gracias!
¿preguntas?

More Related Content

Python Asíncrono - Async Python

  • 1. Python Asíncrono ¿echas de menos las promesas? @javierabadia
  • 7. Javier Abadía VP of Engineering @ StyleSage
  • 10. claves de diccionarios funciones anónimas asincronía
  • 11. ❤ const API_URL = `https://api.openweathermap.org/data/2.5/weather?appid=${API_KEY}&units=metric`; // list of cities const CITIES = [ 'London', 'Tokyo', 'Melbourne', 'Vancouver', 'Lagos', 'Berlin', 'Paris', 'Johannesburg', 'Chicago', 'Mumbai', 'Cairo', 'Beijing' ]; const fetchTemperatureForCity = city => { // For example: ['Lagos', 29.28] return fetch(`${API_URL}&q=${encodeURIComponent(city)}`) .then(response => response.json()) .then(data => [ city, data.main.temp || null ]); } const fetchTemperatureForCities = cities => { return Promise.all(cities.map(fetchTemperatureForCity)) .then(temps => { return temps.reduce((data, [ city, temp ]) => { return { ...data, [city]: Number.isFinite(temp) ? temp.toFixed(2) * 1 : null }; }, {}); }); } fetchTemperatureForCities(CITIES) .then(console.log, console.error);
  • 12. si tuviera que hacer esto en una vista de Django… …no sabría
  • 13. Si Python fuera un bar… BAR
  • 14. BAR Una Hamburguesa, con patatas grandes y una cerveza, por favor Enseguida! esperar esperar servir la cerveza pedir la hamburguesa a la cocina poner las patatatas a freir sacar las patatas de la freidora 1 cliente = 10 min
  • 15. BAR esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora 3 clientes (10, 20, 30) ⋍ 20 min
  • 16. atender más clientes a la vez esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora atender a un cliente en menos tiempo hacer un mejor uso de los recursos
  • 19. Tiempos System Event Actual Latency Scaled Latency One CPU cycle 0.4 ns 1 s Level 1 cache access 0.9 ns 2 s Level 2 cache access 2.8 ns 7 s Level 3 cache access 28 ns 1 min Main memory access (DDR DIMM) ~100 ns 4 min NVMe SSD I/O ~25 μs 17 hrs SSD I/O 50–150 μs 1.5 - 4 days Rotational disk I/O 1–10 ms 1-9 months Internet call: San Francisco to New York City 65 ms 5 years Internet call: San Francisco to Hong Kong 141 ms 11 years
  • 20. url = 'http://example.com/david-hasselhoff.jpg' path = 'media/cool-pics/screen-background.jpg’ r = requests.get(url, stream=True) if r.status_code == 200: with open(path, 'wb') as f: for chunk in r.iter_content(1024): f.write(chunk) im = Image.open(path) im.thumbnail(size, Image.ANTIALIAS) im.save(thumbnail_path, "JPEG") pic = CoolPics(img=path, thumbnail=thumbnail_path) pic.save() varios años varios meses varios meses varios meses varios meses unas pocas horas varios meses < de una hora varios meses
  • 21. url = 'http://example.com/david-hasselhoff.jpg' path = 'media/cool-pics/screen-background.jpg’ r = requests.get(url, stream=True) if r.status_code == 200: with open(path, 'wb') as f: for chunk in r.iter_content(1024): f.write(chunk) im = Image.open(path) im.thumbnail(size, Image.ANTIALIAS) im.save(thumbnail_path, "JPEG") pic = CoolPics(img=path, thumbnail=thumbnail_path) pic.save() varios años varios meses varios meses varios meses varios meses unas pocas horas varios meses < de una hora varios meses naturalezasecuencial x 2000
  • 23. BAR esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora 3 clientes (10, 12, 20) ⋍ 14 min
  • 24. import threading import time class MyThread(threading.Thread): def __init__(self, thread_id, name, delay): super().__init__() self.thread_id = thread_id self.name = name self.delay = delay def run(self): print("Starting " + self.name) # Get lock to synchronize threads # g_thread_lock.acquire() print_time(self.name, 3, self.delay) # Free lock to release next thread # g_thread_lock.release() def print_time(thread_name, counter, delay): while counter: time.sleep(delay) print("%s: %s" % ( thread_name, time.ctime(time.time())) ) counter -= 1 # … # … g_thread_lock = threading.Lock() threads = [] # Create new threads thread1 = MyThread(1, "Thread-1", 1) thread2 = MyThread(2, "Thread-2", 2) # Start new Threads thread1.start() thread2.start() # Add threads to thread list threads.append(thread1) threads.append(thread2) # Wait for all threads to complete for t in threads: t.join() print("Exiting Main Thread") threading
  • 25. import threading import time class MyThread(threading.Thread): def __init__(self, thread_id, name, delay): super().__init__() self.thread_id = thread_id self.name = name self.delay = delay def run(self): print("Starting " + self.name) # Get lock to synchronize threads # g_thread_lock.acquire() print_time(self.name, 3, self.delay) # Free lock to release next thread # g_thread_lock.release() def print_time(thread_name, counter, delay): while counter: time.sleep(delay) print("%s: %s" % ( thread_name, time.ctime(time.time())) ) counter -= 1 # … # … g_thread_lock = threading.Lock() threads = [] # Create new threads thread1 = MyThread(1, "Thread-1", 1) thread2 = MyThread(2, "Thread-2", 2) # Start new Threads thread1.start() thread2.start() # Add threads to thread list threads.append(thread1) threads.append(thread2) # Wait for all threads to complete for t in threads: t.join() print("Exiting Main Thread") crear las threads
  • 26. import threading import time class MyThread(threading.Thread): def __init__(self, thread_id, name, delay): super().__init__() self.thread_id = thread_id self.name = name self.delay = delay def run(self): print("Starting " + self.name) # Get lock to synchronize threads # g_thread_lock.acquire() print_time(self.name, 3, self.delay) # Free lock to release next thread # g_thread_lock.release() def print_time(thread_name, counter, delay): while counter: time.sleep(delay) print("%s: %s" % ( thread_name, time.ctime(time.time())) ) counter -= 1 # … # … g_thread_lock = threading.Lock() threads = [] # Create new threads thread1 = MyThread(1, "Thread-1", 1) thread2 = MyThread(2, "Thread-2", 2) # Start new Threads thread1.start() thread2.start() # Add threads to thread list threads.append(thread1) threads.append(thread2) # Wait for all threads to complete for t in threads: t.join() print("Exiting Main Thread") arrancarlas
  • 27. import threading import time class MyThread(threading.Thread): def __init__(self, thread_id, name, delay): super().__init__() self.thread_id = thread_id self.name = name self.delay = delay def run(self): print("Starting " + self.name) # Get lock to synchronize threads # g_thread_lock.acquire() print_time(self.name, 3, self.delay) # Free lock to release next thread # g_thread_lock.release() def print_time(thread_name, counter, delay): while counter: time.sleep(delay) print("%s: %s" % ( thread_name, time.ctime(time.time())) ) counter -= 1 # … # … g_thread_lock = threading.Lock() threads = [] # Create new threads thread1 = MyThread(1, "Thread-1", 1) thread2 = MyThread(2, "Thread-2", 2) # Start new Threads thread1.start() thread2.start() # Add threads to thread list threads.append(thread1) threads.append(thread2) # Wait for all threads to complete for t in threads: t.join() print("Exiting Main Thread") ¿sincronizarlas?
  • 28. import threading import time class MyThread(threading.Thread): def __init__(self, thread_id, name, delay): super().__init__() self.thread_id = thread_id self.name = name self.delay = delay def run(self): print("Starting " + self.name) # Get lock to synchronize threads # g_thread_lock.acquire() print_time(self.name, 3, self.delay) # Free lock to release next thread # g_thread_lock.release() def print_time(thread_name, counter, delay): while counter: time.sleep(delay) print("%s: %s" % ( thread_name, time.ctime(time.time())) ) counter -= 1 # … # … g_thread_lock = threading.Lock() threads = [] # Create new threads thread1 = MyThread(1, "Thread-1", 1) thread2 = MyThread(2, "Thread-2", 2) # Start new Threads thread1.start() thread2.start() # Add threads to thread list threads.append(thread1) threads.append(thread2) # Wait for all threads to complete for t in threads: t.join() print("Exiting Main Thread") esperar a que terminen
  • 29. thread safety hcp://edot.org/pyfaq/what-kinds-of-global-value-mutafon-are-thread-safe.htm songs = [] songs.append(my_song) songs.pop() songs.sort() artist_ages.keys() i = i+1 songs[i] = songs[j] if songs: song = songs.pop() artist_ages[‘Bob’] += 1
  • 34. BAR BAR esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora esperar poner las patatatas a freir pedir la hamburguesa a la cocina esperar servir la cerveza sacar las patatas de la freidora 3 clientes (10, 10, 20) ⋍ 13.3 min
  • 35. from multiprocessing import Lock, Process, Queue, current_process import time import queue # imported for using queue.Empty exception def do_job(tasks_to_accomplish, tasks_that_are_done): while True: try: task = tasks_to_accomplish.get_nowait() except queue.Empty: break else: print(task) tasks_that_are_done.put(task + ' is done by ' + current_process().name) time.sleep(.5) return True def main(): number_of_task = 10 number_of_processes = 4 tasks_to_accomplish = Queue() tasks_that_are_done = Queue() processes = [] for i in range(number_of_task): tasks_to_accomplish.put("Task no " + str(i)) # creating processes for w in range(number_of_processes): p = Process( target=do_job, args=(tasks_to_accomplish, tasks_that_are_done) ) processes.append(p) p.start() # completing process for p in processes: p.join() # print the output while not tasks_that_are_done.empty(): print(tasks_that_are_done.get()) return True if __name__ == '__main__': main() mulf-processing
  • 36. from multiprocessing import Lock, Process, Queue, current_process import time import queue # imported for using queue.Empty exception def do_job(tasks_to_accomplish, tasks_that_are_done): while True: try: task = tasks_to_accomplish.get_nowait() except queue.Empty: break else: print(task) tasks_that_are_done.put(task + ' is done by ' + current_process().name) time.sleep(.5) return True def main(): number_of_task = 10 number_of_processes = 4 tasks_to_accomplish = Queue() tasks_that_are_done = Queue() processes = [] for i in range(number_of_task): tasks_to_accomplish.put("Task no " + str(i)) # creating processes for w in range(number_of_processes): p = Process( target=do_job, args=(tasks_to_accomplish, tasks_that_are_done) ) processes.append(p) p.start() # completing process for p in processes: p.join() # print the output while not tasks_that_are_done.empty(): print(tasks_that_are_done.get()) return True if __name__ == '__main__': main() multi-processing
  • 37. from multiprocessing import Lock, Process, Queue, current_process import time import queue # imported for using queue.Empty exception def do_job(tasks_to_accomplish, tasks_that_are_done): while True: try: task = tasks_to_accomplish.get_nowait() except queue.Empty: break else: print(task) tasks_that_are_done.put(task + ' is done by ' + current_process().name) time.sleep(.5) return True def main(): number_of_task = 10 number_of_processes = 4 tasks_to_accomplish = Queue() tasks_that_are_done = Queue() processes = [] for i in range(number_of_task): tasks_to_accomplish.put("Task no " + str(i)) # creating processes for w in range(number_of_processes): p = Process( target=do_job, args=(tasks_to_accomplish, tasks_that_are_done) ) processes.append(p) p.start() # completing process for p in processes: p.join() # print the output while not tasks_that_are_done.empty(): print(tasks_that_are_done.get()) return True if __name__ == '__main__': main() multi-processing crear colas IPC
  • 38. from multiprocessing import Lock, Process, Queue, current_process import time import queue # imported for using queue.Empty exception def do_job(tasks_to_accomplish, tasks_that_are_done): while True: try: task = tasks_to_accomplish.get_nowait() except queue.Empty: break else: print(task) tasks_that_are_done.put(task + ' is done by ' + current_process().name) time.sleep(.5) return True def main(): number_of_task = 10 number_of_processes = 4 tasks_to_accomplish = Queue() tasks_that_are_done = Queue() processes = [] for i in range(number_of_task): tasks_to_accomplish.put("Task no " + str(i)) # creating processes for w in range(number_of_processes): p = Process( target=do_job, args=(tasks_to_accomplish, tasks_that_are_done) ) processes.append(p) p.start() # completing process for p in processes: p.join() # print the output while not tasks_that_are_done.empty(): print(tasks_that_are_done.get()) return True if __name__ == '__main__': main() multi-processing crear los procesos
  • 39. from multiprocessing import Lock, Process, Queue, current_process import time import queue # imported for using queue.Empty exception def do_job(tasks_to_accomplish, tasks_that_are_done): while True: try: task = tasks_to_accomplish.get_nowait() except queue.Empty: break else: print(task) tasks_that_are_done.put(task + ' is done by ' + current_process().name) time.sleep(.5) return True def main(): number_of_task = 10 number_of_processes = 4 tasks_to_accomplish = Queue() tasks_that_are_done = Queue() processes = [] for i in range(number_of_task): tasks_to_accomplish.put("Task no " + str(i)) # creating processes for w in range(number_of_processes): p = Process( target=do_job, args=(tasks_to_accomplish, tasks_that_are_done) ) processes.append(p) p.start() # completing process for p in processes: p.join() # print the output while not tasks_that_are_done.empty(): print(tasks_that_are_done.get()) return True if __name__ == '__main__': main() multi-processing ejecutar los procesos
  • 40. from multiprocessing import Lock, Process, Queue, current_process import time import queue # imported for using queue.Empty exception def do_job(tasks_to_accomplish, tasks_that_are_done): while True: try: task = tasks_to_accomplish.get_nowait() except queue.Empty: break else: print(task) tasks_that_are_done.put(task + ' is done by ' + current_process().name) time.sleep(.5) return True def main(): number_of_task = 10 number_of_processes = 4 tasks_to_accomplish = Queue() tasks_that_are_done = Queue() processes = [] for i in range(number_of_task): tasks_to_accomplish.put("Task no " + str(i)) # creating processes for w in range(number_of_processes): p = Process( target=do_job, args=(tasks_to_accomplish, tasks_that_are_done) ) processes.append(p) p.start() # completing process for p in processes: p.join() # print the output while not tasks_that_are_done.empty(): print(tasks_that_are_done.get()) return True if __name__ == '__main__': main() multi-processing esperar a que terminen todos
  • 41. I/O bound CPU bound multi- threading mulf- processing comparten memoria GIL más pesados no comparten memoria IPC
  • 42. Global Interpreter Lock • El intérprete de Python no es thread-safe • Específico de ALGUNAS implementaciones de Python (CPython, PyPy) • Solo una thread puede estar interpretando código Python en un proceso • El intérprete “suelta” el “lock” para operaciones I/O (o incluso NumPy) • Consecuencias: • tareas limitadas por I/O van más rápido con múltiples threads • tareas limitadas por CPU van más lento con múltiples threads
  • 43. más rápido ¡más lento! (por culpa del GIL) más rápido (pero más ”caro”) más rápido I/O bound CPU bound multi- threading multi- processing comparten memoria GIL más pesados no comparten memoria IPC
  • 44. pero… ¿cómo lo hace JavaScript?
  • 45. BAR servir la cerveza pedir la hamburguesa a la cocina poner las patatatas a freir sacar las patatas de la freidora servir la cerveza pedir la hamburguesa a la cocina poner las patatatas a freir sacar las patatas de la freidora servir la cerveza pedir la hamburguesa a la cocina poner las patatatas a freir sacar las patatas de la freidora 3 clientes (4,5,7) ⋍ 5,33 min
  • 46. Modelo de Concurrencia de JavaScript • en JavaScript solo hay una thread de usuario • el código se ejecuta “por turnos”, sin ninguna interrupción, siempre hasta el final • dentro de un “bucle de eventos” • TODAS las operaciones de I/O son asíncronas: • se lanzan en el momento • el código no espera a que se termine la ejecución • el resultado se recibe en un callback (o una Promesa) que se ejecutará en un turno en el futuro • También son asíncronas todas las funciones que llaman a una función asíncrona https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
  • 50. event loopevent loopevent loopevent loop TURNO 1 TURNO 2
  • 51. Python asyncio - async/await Python 3.4+
  • 52. # https://docs.python.org/3/library/asyncio-task.html import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): print(f"started at {time.strftime('%X')}") await say_after(1, 'hello') await say_after(2, 'world') print(f"finished at {time.strftime('%X')}") asyncio.run(main()) bucle de eventos await ≈ yield
  • 53. import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): print(f"started at {time.strftime('%X')}") t1 = say_after(1, 'hello') t2 = say_after(2, 'world') await asyncio.gather(t1, t2) print(f"finished at {time.strftime('%X')}") asyncio.run(main())
  • 56. si no hay cooperación, no funciona • en JavaScript la cooperación es obligatoria • todas las librerías son asíncronas • en Python casi todas las librerías son síncronas (no cooperan) nuestro código no debe bloquearse debe devolver el control al bucle de eventos no hay un “scheduler pre-emptivo”
  • 57. el problema es… • todo lo que NO podemos usar • http, requests • acceso a bases de datos • I/O de ficheros • necesitamos nuevas librerías para todo • aiohttp • aiopg • aiomysql • aiofiles • https://github.com/timofurrer/awesome-asyncio
  • 59. es difícil • converfr un proyecto existente en async • o una parte • en un nuevo proyecto async • encontrar y usar librerías • p.ej script que se conecta a nuestra cuenta de GitHub y crea un informe de branches en Excel • pygithub - hace llamadas de red • openpyxl - lee y escribe ficheros
  • 63. concurrent.futures • librería de alto nivel • usando multi-processing o multi-threading con la misma interfaz • proporciona una sintaxis parecida a las promesas • compatible con todas las librerías síncronas • si son thread-safe podemos usar multi-threading • si no, tendremos que usar multi-processing Python 3.22011-02-20
  • 64. import threading from concurrent import futures from random import random from time import sleep import requests nums = range(1, 11) url_tpl = "http://jsonplaceholder.typicode.com/todos/{}" def get_data(myid): url = url_tpl.format(myid) data = requests.get(url).json() sleep(random() * 5) return data def main(): with futures.ThreadPoolExecutor(4) as executor: results = executor.map(get_data, nums) print() for result in results: print(result) if __name__ == '__main__': main() futures
  • 65. import threading from concurrent import futures from random import random from time import sleep import requests nums = range(1, 11) url_tpl = "http://jsonplaceholder.typicode.com/todos/{}" def get_data(myid): url = url_tpl.format(myid) data = requests.get(url).json() sleep(random() * 5) return data def main(): with futures.ThreadPoolExecutor(4) as executor: results = executor.map(get_data, nums) print() for result in results: print(result) if __name__ == '__main__': main() futures ejecutar en 4 threads
  • 66. import threading from concurrent import futures from random import random from time import sleep import requests nums = range(1, 11) url_tpl = "http://jsonplaceholder.typicode.com/todos/{}" def get_data(myid): url = url_tpl.format(myid) data = requests.get(url).json() sleep(random() * 5) return data def main(): with futures.ThreadPoolExecutor(4) as executor: results = executor.map(get_data, nums) print() for result in results: print(result) if __name__ == '__main__': main() futures ¡librerías síncronas!
  • 67. def main(): # with futures.ProcessPoolExecutor(4) as executor: with futures.ThreadPoolExecutor(4) as executor: jobs = [executor.submit(get_data, num) for num in nums] for comp_job in futures.as_completed(jobs): print(comp_job.result()) futures resultados según están disponibles
  • 68. def main(): with futures.ThreadPoolExecutor(5) as executor: jobs = [executor.submit(get_img, num) for num in range(20)] for comp_job in futures.as_completed(jobs): img_path = comp_job.result() executor.submit(add_meme_to_img, img_path) futures pasos encadenados
  • 69. def main(): # with futures.ProcessPoolExecutor(4) as executor: with futures.ThreadPoolExecutor(4) as executor: jobs = [executor.submit(get_data, num) for num in nums] done, not_done = futures.wait(jobs, return_when=futures.FIRST_COMPLETED) print(done.pop().result()) print(len(not_done)) for not_done_job in not_done: not_done_job.cancel() print() print('finished') futures “carreras” de futuros
  • 70. ¿y lo de la thread-safety? • evitar los efectos secundarios o variables compartidas • funciones puras - programación funcional • cuidado con las librerías que usamos, pueden no ser thread-safe
  • 71. Opciones • Multithreading • Multiprocessing • Cooperative concurrency Async/Await • concurrent.futures • Tornado/Twisted • Curio/Trio • Django 3.x ?
  • 72. ¿y qué pasa con Django?
  • 73. vistas con múlfples llamadas I/O (disco/red/bd) vistas con múlfples llamadas I/O (disco/red/bd) Asincronía en Django tareas de larga duración fuera del ciclo de request/response websockets con muchos clientes ¡ESTO! async/await celery
  • 76. https://www.aeracode.org/2018/06/04/django-async-roadmap/ Request path • WSGI - ASGI handler • URL routing • Middlewares • Views ORM Templates Forms Caching Sessions Authentication Admin Email Static files Signals
  • 77. DjangoCon 2019 - Just Add Await: Retrofitting Async Into Django by Andrew Godwin https://www.youtube.com/watch?v=d9BAUBEyFgM
  • 79. Timeline • Python 3.2 (2011-02-20) • concurrent.futures • Python 3.4 (2014-03-16) • asyncio provisional • Python 3.5 (2015-09-13) • PEP 492, coroufnes with async and await syntax. • Python 3.6 (2016-12-23) • PEP 525: Asynchronous Generators • PEP 530: Asynchronous Comprehensions • asyncio stable • Python 3.7 (2018-06-27) • asyncio faceli| • Python 3.8 (2019-10-14) • async REPL • Twisted (2003) • Netty (2004) • Java • Node.js (2009) • JavaScript • non-blocking by default • npm • Tornado (2010)
  • 80. resumiendo • el mundo es asíncrono • muchas aplicaciones son I/O bound • podemos usar mejor los recursos: • multi-proceso, • multi-thread • concurrencia cooperativa • cada tarea se puede completar en menos tiempo • paralelizar acciones necesarias para completar una tarea (a veces) • con los mismos recursos puedo ejecutar más tareas • paralelizar acciones necesarias para múltiples tareas