La réponse de Jake est bonne, mais si vous ne voulez pas utiliser un pool de threads (vous ne savez pas combien de threads vous aurez besoin, mais créez-les au besoin), alors un bon moyen de transmettre des informations entre les threads est la fonction intégrée Classe Queue.Queue , car elle offre la sécurité des threads.
J'ai créé le décorateur suivant pour le faire agir de manière similaire au threadpool:
def threaded(f, daemon=False):
import Queue
def wrapped_f(q, *args, **kwargs):
'''this function calls the decorated function and puts the
result in a queue'''
ret = f(*args, **kwargs)
q.put(ret)
def wrap(*args, **kwargs):
'''this is the function returned from the decorator. It fires off
wrapped_f in a new thread and returns the thread object with
the result queue attached'''
q = Queue.Queue()
t = threading.Thread(target=wrapped_f, args=(q,)+args, kwargs=kwargs)
t.daemon = daemon
t.start()
t.result_queue = q
return t
return wrap
Ensuite, vous l'utilisez simplement comme:
@threaded
def long_task(x):
import time
x = x + 5
time.sleep(5)
return x
# does not block, returns Thread object
y = long_task(10)
print y
# this blocks, waiting for the result
result = y.result_queue.get()
print result
La fonction décorée crée un nouveau thread à chaque appel et renvoie un objet Thread qui contient la file d'attente qui recevra le résultat.
METTRE À JOUR
Cela fait un bon moment que j'ai posté cette réponse, mais elle obtient toujours des vues, alors j'ai pensé que je la mettrais à jour pour refléter la façon dont je fais cela dans les nouvelles versions de Python:
Python 3.2 ajouté dans le concurrent.futures
module qui fournit une interface de haut niveau pour les tâches parallèles. Il fournit ThreadPoolExecutor
et ProcessPoolExecutor
vous pouvez donc utiliser un thread ou un pool de processus avec la même API.
Un avantage de cette API est que la soumission d'une tâche à un Executor
retourne un Future
objet, qui se terminera avec la valeur de retour de l'appelable que vous soumettez.
Cela rend queue
inutile d' attacher un objet, ce qui simplifie un peu le décorateur:
_DEFAULT_POOL = ThreadPoolExecutor()
def threadpool(f, executor=None):
@wraps(f)
def wrap(*args, **kwargs):
return (executor or _DEFAULT_POOL).submit(f, *args, **kwargs)
return wrap
Cela utilisera un exécuteur de pool de threads de module par défaut s'il n'est pas transmis.
L'utilisation est très similaire à celle d'avant:
@threadpool
def long_task(x):
import time
x = x + 5
time.sleep(5)
return x
# does not block, returns Future object
y = long_task(10)
print y
# this blocks, waiting for the result
result = y.result()
print result
Si vous utilisez Python 3.4+, une fonctionnalité vraiment intéressante de l'utilisation de cette méthode (et des objets Future en général) est que le futur retourné peut être encapsulé pour le transformer en un asyncio.Future
avec asyncio.wrap_future
. Cela le fait fonctionner facilement avec les coroutines:
result = await asyncio.wrap_future(long_task(10))
Si vous n'avez pas besoin d'accéder à l' concurrent.Future
objet sous-jacent , vous pouvez inclure l'habillage dans le décorateur:
_DEFAULT_POOL = ThreadPoolExecutor()
def threadpool(f, executor=None):
@wraps(f)
def wrap(*args, **kwargs):
return asyncio.wrap_future((executor or _DEFAULT_POOL).submit(f, *args, **kwargs))
return wrap
Ensuite, chaque fois que vous devez pousser le code intensif ou bloquant du processeur hors du thread de la boucle d'événements, vous pouvez le mettre dans une fonction décorée:
@threadpool
def some_long_calculation():
...
# this will suspend while the function is executed on a threadpool
result = await some_long_calculation()
futures = [executor.submit(foo, param) for param in param_list]
La commande sera maintenue et la sortie duwith
permettra la collecte des résultats.[f.result() for f in futures]