Django long exécution de tâches asynchrones avec threads / processing

Avertissement: je sais qu'il y a plusieurs questions similaires sur. Je pense que j'ai lu la plupart sinon la totalité d'entre eux, mais je n'ai pas trouvé de réponse à ma vraie question (Voir plus loin). Je sais aussi que l'utilisation de celery ou d'autres systèmes de file d'attente asynchrones est le meilleur moyen d'accomplir des tâches de longue durée - ou du moins d'utiliser un script géré par cron. Il y a aussi mod_wsgi doc sur les processus et les threads mais je ne suis pas sûr d'avoir tout compris.

la question Est:

quels sont les risques/problèmes liés à l'utilisation des solutions énumérées là-bas? Est l'un d'eux viable pour les tâches longues (ok, même si le céleri est mieux)? Ma question est vraiment plus sur la compréhension des internes de WSGI et python / django que sur la recherche de la meilleure solution globale. Problèmes de blocage des threads, accès non sécurisé aux variables, traitement des zombies, etc.

disons:

  1. mon "long_process" fait quelque chose vraiment à l'abri. même si elle échoue, je n'ai pas de soins.
  2. python >= 2.6
  3. j'utilise mod_wsgi avec apache (est-ce que quelque chose va changer avec uwsgi ou gunicorn?) en mode démon

mod_wsgi conf:

WSGIDaemonProcess NAME user=www-data group=www-data threads=25
WSGIScriptAlias / /path/to/wsgi.py
WSGIProcessGroup %{ENV:VHOST}

j'ai pensé que ce sont les options disponibles pour le lancement de séparer (au sens large du terme) accomplir une tâche de longue haleine tout en répondant rapidement aux utilisateur:

os.fourche

import os

if os.fork()==0:
    long_process()
else:
    return HttpResponse()

sous-processus

import subprocess

p = subprocess.Popen([sys.executable, '/path/to/script.py'], 
                                    stdout=subprocess.PIPE, 
                                    stderr=subprocess.STDOUT)

(lorsque le script est susceptible d'être manage.py commande)

threads

import threading

t = threading.Thread(target=long_process,
                             args=args,
                             kwargs=kwargs)
t.setDaemon(True)
t.start()
return HttpResponse()

NB.

en raison du verrouillage global de L'interpréteur, dans CPython, un seul thread peut exécuter du code Python à la fois (même si certaines bibliothèques axées sur la performance peuvent surmonter cette limitation). Si vous souhaitez que votre application pour faire une meilleure utilisation de l'informatique ressources des machines multi-core, il vous est conseillé d'utiliser multiprocessing. Cependant, le threading est toujours un modèle approprié si vous voulez exécuter plusieurs tâches liées par des entrées/sorties simultanément.

le thread principal va rapidement revenir (le httpresponse). Est-ce que le bloc de thread long généré WSGI fera quelque chose d'autre pour une autre requête?!

multitraitement

from multiprocessing import Process

p = Process(target=_bulk_action,args=(action,objs))
p.start()
return HttpResponse()

ceci devrait résoudre la question de la simultanéité du fil, ne devrait pas il?


voilà donc les options auxquelles je pourrais penser. Ce qui peut fonctionner et ce n'est pas, et pourquoi?

29
demandé sur Stefano 2011-11-09 21:23:47

3 réponses

os.fourche

Une fourchette va cloner le processus parent, qui, dans ce cas, votre Django pile. Puisque vous voulez simplement lancer un script python séparé, cela semble être une quantité inutile de bloat.

sous-processus

en utilisant subprocess devrait être interactif. En d'autres termes, bien que vous puissiez utiliser ceci pour générer efficacement un processus, on s'attend à ce qu'à un moment donné vous le terminiez une fois terminé. C'est Python possible pourrait nettoyer pour vous si vous laissez un fonctionnement, mais ma conjecture serait que cela résultera réellement en une fuite de mémoire.

threads

les Threads sont des unités logiques définies. Ils commencent quand leur run() la méthode est appelée, et se termine quand le run() fin de l'exécution de method. Cela les rend bien adaptés à la création d'une branche de logique qui se déroulera en dehors de la portée actuelle. Cependant, comme vous l'avez mentionné, ils sont soumis à la Mondiale Verrouillage De L'Interprète.

multitraitement

il s'agit essentiellement de fils sur les stéroïdes. Il a les avantages d'un thread, mais n'est pas soumis au verrouillage global de L'interpréteur, et peut profiter des architectures multi-core. Toutefois, leur travail est plus compliqué.

donc, vos choix se résument vraiment à des threads ou des multiprocesseurs. Si vous pouvez passer avec un fil et il a du sens pour votre application, aller avec un fil. Sinon, utilisez le multitraitement.

26
répondu Chris Pratt 2011-11-09 20:06:40

j'ai trouvé que l'utilisation de uWSGI Decorators tout à fait plus simple que D'utiliser du céleri si vous avez besoin d'exécuter une tâche longue en arrière-plan. Pensez céleri est la meilleure solution pour les projets lourds graves, et il est au-dessus de la tête pour faire quelque chose de simple.

Pour commencer à utiliser uWSGI Decorators vous avez juste besoin de mettre à jour votre config uWSGI avec

<spooler-processes>1</spooler-processes>
<spooler>/here/the/path/to/dir</spooler>

écrire du code comme ceci:

@spoolraw
def long_task(arguments):
    try:
        doing something with arguments['myarg'])
    except Exception as e:
        ...something...
    return uwsgi.SPOOL_OK

def myView(request)
    long_task.spool({'myarg': str(someVar)})
    return render_to_response('done.html')

que lorsque vous démarrez view dans uWSGI log s'affiche:

[spooler] written 208 bytes to file /here/the/path/to/dir/uwsgi_spoolfile_on_hostname_31139_2_0_1359694428_441414

et lorsque la tâche terminée:

[spooler /here/the/path/to/dir pid: 31138] done with task uwsgi_spoolfile_on_hostname_31139_2_0_1359694428_441414 after 78 seconds

Il est étrange(pour moi) restrictions:

    - spool can receive as argument only dictionary of strings, look like because it's serialize in file as strings.
    - spool should be created on start up so "spooled" code it should be contained in separate file which should be defined in uWSGI config as <import>pyFileWithSpooledCode</import>
11
répondu Oleg Neumyvakin 2015-03-20 12:09:54

la question:

est-ce que le fil long généré bloque wsgi de faire quelque chose d'autre pour une autre demande?!

la réponse est non.

vous devez encore être prudent en créant des threads d'arrière-plan à partir d'une requête mais dans le cas où vous créez simplement un grand nombre d'entre eux et bloquez tout le processus. Vous avez vraiment besoin d'un système de mise en file d'attente des tâches même si vous faites des choses en cours.

à l'égard de la fabrication d'une fourchette ou d'une exec processus web, en particulier à partir D'Apache qui n'est généralement pas une bonne idée car Apache peut imposer des conditions étranges sur l'environnement du sous-processus créé qui pourrait techniquement interférer avec son fonctionnement.

utiliser un système comme le céleri reste probablement la meilleure solution.

3
répondu Graham Dumpleton 2011-11-09 22:14:53