web-dev-qa-db-fra.com

Python - À quoi sert queue.task_done ()?

J'ai écrit un script qui a plusieurs threads (créés avec threading.Thread) Récupérant les URL à partir d'un Queue en utilisant queue.get_nowait(), puis en traitant le HTML. Je suis nouveau dans la programmation multi-thread et j'ai du mal à comprendre le but de la fonction queue.task_done().

Lorsque Queue est vide, il renvoie automatiquement l'exception queue.Empty. Je ne comprends donc pas la nécessité pour chaque thread d'appeler la fonction task_done(). Nous savons que nous avons terminé avec la file d'attente lorsqu'elle est vide, alors pourquoi devons-nous l'informer que les threads de travail ont terminé leur travail (ce qui n'a rien à voir avec la file d'attente, après avoir obtenu l'URL de celle-ci) ?

Quelqu'un pourrait-il me fournir un exemple de code (idéalement en utilisant urllib, des E/S de fichiers ou autre chose que des nombres fibonacci et en imprimant "Bonjour") qui me montre comment cette fonction serait utilisée dans des applications pratiques?

11
J. Taylor

Queue.task_done n'est pas là pour le bénéfice des travailleurs. Il est là pour soutenir Queue.join.


Si je vous donne une boîte de tâches, est-ce que je me soucie quand vous avez tout sorti de la boîte?

Non. Je me soucie quand le travail est terminé . Regarder une boîte vide ne me le dit pas. Vous et 5 autres gars travaillez peut-être encore sur des trucs que vous avez sortis de la boîte.

Queue.task_done permet aux utilisateurs de dire quand une tâche est terminée . Quelqu'un attend que tout le travail soit fait avec Queue.join attendra suffisamment task_done des appels ont été effectués, pas lorsque la file d'attente est vide.

13
user2357112

Quelqu'un pourrait-il me fournir un exemple de code (idéalement en utilisant urllib, des E/S de fichiers ou autre chose que des nombres fibonacci et en imprimant "Bonjour") qui me montre comment cette fonction serait utilisée dans des applications pratiques?

@ user2357112 réponse explique bien le but de task_done, mais il manque l'exemple demandé. Voici une fonction qui calcule les sommes de contrôle d'un nombre arbitraire de fichiers et renvoie un dict mappant chaque nom de fichier à la somme de contrôle correspondante. À l'intérieur de la fonction, le travail est divisé en plusieurs fils.

La fonction utilise de Queue.join pour attendre que les employés aient terminé les tâches qui leur ont été assignées, il est donc prudent de renvoyer le dictionnaire à l'appelant. C'est un moyen pratique d'attendre que tous les fichiers soient traités , au lieu d'être simplement retirés de la file d'attente.

import threading, queue, hashlib

def _work(q, checksums):
    while True:
        filename = q.get()
        if filename is None:
            q.put(None)
            break
        try:
            sha = hashlib.sha256()
            with open(filename, 'rb') as f:
                for chunk in iter(lambda: f.read(65536), b''):
                    sha.update(chunk)
            checksums[filename] = sha.digest()
        finally:
            q.task_done()

def calc_checksums(files):
    q = queue.Queue()
    checksums = {}
    for i in range(1):
        threading.Thread(target=_work, args=(q, checksums)).start()
    for f in files:
        q.put(f)
    q.join()
    q.put(None)  # tell workers to exit
    return checksums

Une note sur le GIL: puisque le code dans hashlib libère en interne le GIL lors du calcul de la somme de contrôle, l'utilisation de plusieurs threads donne un mesurable (1.75x-2x selon Python version) accélération par rapport à la variante à filetage unique.

2
user4815162342

.task_done() est utilisé pour marquer .join() que le traitement est terminé.

???? Si vous utilisez .join() et n'appelez pas .task_done() pour chaque élément traité, votre script se bloquera pour toujours.


Ce n'est rien comme un petit exemple;

import logging
import queue
import threading
import time

items_queue = queue.Queue()
running = False


def items_queue_worker():
    while running:
        try:
            item = items_queue.get(timeout=0.01)
            if item is None:
                continue

            try:
                process_item(item)
            finally:
                items_queue.task_done()

        except queue.Empty:
            pass
        except:
            logging.exception('error while processing item')


def process_item(item):
    print('processing {} started...'.format(item))
    time.sleep(0.5)
    print('processing {} done'.format(item))


if __name__ == '__main__':
    running = True

    # Create 10 items_queue_worker threads
    worker_threads = 10
    for _ in range(worker_threads):
        threading.Thread(target=items_queue_worker).start()

    # Populate your queue with data
    for i in range(100):
        items_queue.put(i)

    # Wait for all items to finish processing
    items_queue.join()

    running = False
2
Jossef Harush

"Lisez la source, Luke!" - Obi-one Codobi

La source de ayncio.queue est assez courte.

  • le nombre de tâches inachevées augmente d'une unité lorsque vous mettez dans la file d'attente.
  • il descend par un avec vous appelez task_done
  • join () attend qu'il n'y ait aucune tâche inachevée.

Cela rend join utile si et seulement si vous appelez task_done (). En utilisant l'analogie bancaire classique:

  • les gens entrent dans les portes et font la queue; door est un producteur qui fait un q.put ()
  • lorsqu'un caissier est inactif et qu'une personne est en ligne, il se rend à la fenêtre du caissier. le caissier fait un q.get ().
  • Lorsque le caissier a fini d'aider la personne, il est prêt pour la suivante. le caissier fait q.task_done ()
  • à 17 h, les portes sont verrouillées la tâche de porte se termine
  • vous attendez que la ligne soit vide et chaque caissier a fini d'aider la personne devant lui. attendez q.join (scrutateurs)
  • puis vous renvoyez les scrutateurs à la maison, qui sont maintenant tous au ralenti avec une file d'attente vide. pour le caissier dans les guichets: teller.cancel ()

Sans task_done (), vous ne pouvez pas savoir que chaque caissier a fini avec les gens. Vous ne pouvez pas envoyer un caissier à la maison alors qu'il a une personne à sa fenêtre.

0
Charles Merriam