web-dev-qa-db-fra.com

Comment combiner python asyncio avec des threads?

J'ai réussi à créer un microservice RESTful avec Python asyncio et aiohttp qui écoute un événement POST pour collecter des événements en temps réel à partir de divers feeders) .

Il crée ensuite une structure en mémoire pour mettre en cache les dernières 24 heures des événements dans une structure imbriquée par défaut/deque.

Maintenant, je voudrais vérifier périodiquement cette structure sur le disque, de préférence en utilisant du cornichon.

Étant donné que la structure de la mémoire peut être> 100 Mo, je voudrais éviter de retarder le traitement des événements entrants pendant le temps nécessaire pour vérifier la structure.

Je préfère créer une copie instantanée (par exemple une copie profonde) de la structure, puis prendre mon temps pour l'écrire sur le disque et répéter sur un intervalle de temps prédéfini.

J'ai cherché des exemples sur la façon de combiner les threads (et est-ce même le meilleur solution pour cela?) Et asyncio à cette fin, mais je n'ai pas trouvé quelque chose qui pourrait m'aider.

Tous les conseils pour commencer sont très appréciés!

32
fxstein

Il est assez simple de déléguer une méthode à un thread ou à un sous-processus en utilisant BaseEventLoop.run_in_executor :

import asyncio
import time
from concurrent.futures import ProcessPoolExecutor

def cpu_bound_operation(x):
    time.sleep(x) # This is some operation that is CPU-bound

@asyncio.coroutine
def main():
    # Run cpu_bound_operation in the ProcessPoolExecutor
    # This will make your coroutine block, but won't block
    # the event loop; other coroutines can run in meantime.
    yield from loop.run_in_executor(p, cpu_bound_operation, 5)


loop = asyncio.get_event_loop()
p = ProcessPoolExecutor(2) # Create a ProcessPool with 2 processes
loop.run_until_complete(main())

Quant à savoir si utiliser un ProcessPoolExecutor ou ThreadPoolExecutor, c'est un peu difficile à dire; décapage d'un gros objet va certainement manger quelques cycles de processeur, ce qui au départ vous ferait penser que ProcessPoolExecutor est la voie à suivre. Cependant, passer votre objet de 100 Mo à un Process dans le pool nécessiterait de décaper l'instance dans votre processus principal, d'envoyer les octets au processus enfant via IPC, de le décompresser dans l'enfant, puis de le décaper à nouveau afin que vous puissiez l'écrire sur le disque. Compte tenu de cela, je suppose que les frais généraux de décapage/décapage seront suffisamment importants pour que vous fassiez mieux d'utiliser un ThreadPoolExecutor, même si vous allez subir une baisse de performances en raison du GIL.

Cela dit, il est très simple de tester les deux façons et de s'en assurer, alors autant le faire.

50
dano

J'ai également utilisé run_in_executor, Mais j'ai trouvé cette fonction un peu grossière dans la plupart des circonstances, car elle nécessite partial() pour les arguments de mot clé et je ne l'appelle jamais avec autre chose qu'un seul exécuteur et le boucle d'événements par défaut. J'ai donc créé un emballage pratique avec des valeurs par défaut raisonnables et une gestion automatique des arguments de mots clés.

from time import sleep
import asyncio as aio
loop = aio.get_event_loop()

class Executor:
    """In most cases, you can just use the 'execute' instance as a
    function, i.e. y = await execute(f, a, b, k=c) => run f(a, b, k=c) in
    the executor, assign result to y. The defaults can be changed, though,
    with your own instantiation of Executor, i.e. execute =
    Executor(nthreads=4)"""
    def __init__(self, loop=loop, nthreads=1):
        from concurrent.futures import ThreadPoolExecutor
        self._ex = ThreadPoolExecutor(nthreads)
        self._loop = loop
    def __call__(self, f, *args, **kw):
        from functools import partial
        return self._loop.run_in_executor(self._ex, partial(f, *args, **kw))
execute = Executor()

...

def cpu_bound_operation(t, alpha=30):
    sleep(t)
    return 20*alpha

async def main():
    y = await execute(cpu_bound_operation, 5, alpha=-2)

loop.run_until_complete(main())
5
enigmaticPhysicist