web-dev-qa-db-fra.com

Multithreading pour Python Django

Certaines fonctions doivent s'exécuter de manière asynchrone sur le serveur Web. L'envoi d'e-mails ou le post-traitement de données sont des cas d'utilisation typiques.

Quelle est la meilleure façon (ou la plupart Pythonic) d’écrire une fonction de décorateur pour exécuter une fonction de manière asynchrone?

Ma configuration est courante: Python, Django, Gunicorn ou Waitress, AWS EC2 standard Linux

Par exemple, voici un début:

from threading import Thread

def postpone(function):
    def decorator(*args, **kwargs):
        t = Thread(target = function, args=args, kwargs=kwargs)
        t.daemon = True
        t.start()
    return decorator

utilisation souhaitée:

@postpone
def foo():
    pass #do stuff
31
tomcounsell

J'ai continué à utiliser cette implémentation à grande échelle et en production sans aucun problème.

Définition du décorateur:

def start_new_thread(function):
    def decorator(*args, **kwargs):
        t = Thread(target = function, args=args, kwargs=kwargs)
        t.daemon = True
        t.start()
    return decorator

Exemple d'utilisation:

@start_new_thread
def foo():
  #do stuff

Au fil du temps, la pile s’est mise à jour et a fait la transition sans faute.

À l'origine, Python 2.4.7, Django 1.4, Gunicorn 0.17.2, maintenant Python 3.6, Django 2.1, Waitress 1.1.

Si vous utilisez des transactions de base de données, Django créera une nouvelle connexion qui doit être fermée manuellement:

from Django.db import connection

@postpone
def foo():
  #do stuff
  connection.close()
52
tomcounsell

Celery est une file d'attente de tâches/travaux asynchrone. C'est bien documenté et parfait pour ce dont vous avez besoin. Je vous suggère de commencer ici

13
Glyn Jackson

La manière la plus courante de traiter asynchrone dans Django consiste à utiliser Céleri et Django-celery .

2
Thomas Orozco

l'approche de tomcounsell fonctionne bien s'il n'y a pas trop de tâches entrantes. Si de nombreux travaux de longue durée sont exécutés sur une courte période, le processus principal en souffrira donc. Dans ce cas, vous pouvez utiliser un pool de threads avec une coroutine,

# in my_utils.py

from concurrent.futures import ThreadPoolExecutor

MAX_THREADS = 10


def run_thread_pool():
    """
    Note that this is not a normal function, but a coroutine.
    All jobs are enqueued first before executed and there can be
    no more than 10 threads that run at any time point.
    """
    with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
        while True:
            func, args, kwargs = yield
            executor.submit(func, *args, **kwargs)


pool_wrapper = run_thread_pool()

# Advance the coroutine to the first yield (priming)
next(pool_wrapper)
from my_utils import pool_wrapper

def job(*args, **kwargs):
    # do something

def handle(request):
    # make args and kwargs
    pool_wrapper.send((job, args, kwargs))
    # return a response
0
bombs