web-dev-qa-db-fra.com

Queue vs JoinableQueue in Python

Dans Python lors de l'utilisation du module de multi-traitement, il existe 2 types de files d'attente:

  • Queue
  • JoinableQueue.

Quelle est la différence entre eux?

Queue

from multiprocessing import Queue
q = Queue()
q.put(item) # Put an item on the queue
item = q.get() # Get an item from the queue

JoinableQueue

from multiprocessing import JoinableQueue
q = JoinableQueue()
q.task_done() # Signal task completion
q.join() # Wait for completion
19
axcelenator

JoinableQueue a des méthodes join() et task_done(), ce que Queue n'a pas.


classe multiprocessing.Queue ([maxsize])

Renvoie une file d'attente partagée de processus implémentée à l'aide d'un canal et de quelques verrous/sémaphores. Lorsqu'un processus place pour la première fois un élément dans la file d'attente, un thread d'alimentation est démarré qui transfère les objets d'un tampon dans le canal.

Les exceptions habituelles Queue.Empty et Queue.Full du module Queue de la bibliothèque standard sont déclenchées pour signaler les dépassements de délai.

Queue implémente toutes les méthodes de Queue.Queue à l'exception de task_done () et join ().


classe multiprocessing.JoinableQueue ([maxsize])

JoinableQueue, une sous-classe Queue, est une file d'attente qui a en outre des méthodes task_done () et join ().

task_done ()

Indiquez qu'une tâche précédemment mise en file d'attente est terminée. Utilisé par les threads de consommateur de file d'attente. Pour chaque get () utilisée pour récupérer une tâche, un appel ultérieur à task_done () indique à la file d'attente que le traitement de la tâche est terminé.

Si une jointure () est actuellement bloquée, elle reprendra lorsque tous les éléments auront été traités (ce qui signifie qu'un appel task_done () a été reçu pour chaque élément qui a été placé () dans la file d'attente).

Déclenche une ValueError si elle est appelée plus de fois qu'il n'y avait d'éléments placés dans la file d'attente.

join ()

Bloquer jusqu'à ce que tous les éléments de la file d'attente aient été récupérés et traités.

Le nombre de tâches inachevées augmente chaque fois qu'un élément est ajouté à la file d'attente. Le nombre diminue chaque fois qu'un thread consommateur appelle task_done () pour indiquer que l'élément a été récupéré et que tout le travail est terminé. Lorsque le nombre de tâches inachevées tombe à zéro, join () débloque.


Si vous utilisez JoinableQueue, vous devez appeler JoinableQueue.task_done() pour chaque tâche supprimée de la file d'attente, sinon le sémaphore utilisé pour compter le nombre de tâches inachevées peut éventuellement déborder, ce qui déclenche une exception.

18
fferri

D'après la documentation, il est difficile de s'assurer que Queue est réellement vide. Avec JoinableQueue, vous pouvez attendre que la file d'attente se vide en appelant q.join(). Dans les cas où vous souhaitez terminer le travail dans des lots distincts où vous faites quelque chose de discret à la fin de chaque lot, cela peut être utile.

Par exemple, vous pouvez peut-être traiter 1000 éléments à la fois dans la file d'attente, puis envoyer une notification Push à un utilisateur indiquant que vous avez terminé un autre lot. Ce serait difficile à implémenter avec un Queue normal.

Cela pourrait ressembler à:

import multiprocessing as mp

BATCH_SIZE = 1000
STOP_VALUE = 'STOP'

def consume(q):
  for item in iter(q.get, STOP_VALUE):
    try:
      process(item)
    # Be very defensive about errors since they can corrupt pipes.
    except Exception as e:
      logger.error(e)
    finally:
      q.task_done()

q = mp.JoinableQueue()
with mp.Pool() as pool:
  # Pull items off queue as fast as we can whenever they're ready.
  for _ in range(mp.cpu_count()):
    pool.apply_async(consume, q)
  for i in range(0, len(URLS), BATCH_SIZE):
    # Put `BATCH_SIZE` items in queue asynchronously.
    pool.map_async(expensive_func, URLS[i:i+BATCH_SIZE], callback=q.put)
    # Wait for the queue to empty.
    q.join()
    notify_users()
  # Stop the consumers so we can exit cleanly.
  for _ in range(mp.cpu_count()):
    q.put(STOP_VALUE)

NB: Je n'ai pas réellement exécuté ce code. Si vous retirez des éléments de la file d'attente plus rapidement que vous ne les avez mis, vous pourriez finir tôt. Dans ce cas, ce code envoie une mise à jour AT MOINS tous les 1000 éléments, et peut-être plus souvent. Pour les mises à jour de progression, c'est probablement correct. S'il est important d'être exactement 1000, vous pouvez utiliser un mp.Value('i', 0) et vérifiez qu'il vaut 1000 chaque fois que votre join se libère.

1
Ben