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?
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.
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.
.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
"Lisez la source, Luke!" - Obi-one Codobi
La source de ayncio.queue est assez courte.
Cela rend join utile si et seulement si vous appelez task_done (). En utilisant l'analogie bancaire classique:
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.