La gestion des Threads en Python 3

Gestion Des Threads En Python

Un thread est un flux de contrôle qui partage l’état global (mémoire de l’ordinateur) avec d’autres threads ; tous les threads semblent s’exécuter simultanément, bien qu’ils ” se relayent ” généralement sur un seul processeur/noyau de processeur (cpu core en anglais).

Les threads ne sont pas faciles à maîtriser et les programmes multithreads sont souvent difficiles à tester et à déboguer ; cependant, si utilisé correctement, le multithreading peut parfois améliorer les performances d’un programme par rapport à la programmation traditionnelle “single-threaded” ou à “unique thread”.

Cet article ainsi que d’autres couvrent les fonctionnalités que Python fournit pour traiter les threads, y compris les modules threading, queue et “con current”.

Threads en Python

Python offre le multithreading sur les plates-formes qui supportent les threads, telles que Win32, Linux, et d’autres variantes d’Unix. Une action est dite atomique lorsqu’il est garanti qu’il n’y a pas de commutation de thread entre le début et la fin de l’action.

En pratique, en CPython, les opérations qui paraissent atomiques (par exemple, les assignations et les accès simples) sont le plus souvent atomiques, lorsqu’elles sont exécutées sur des types intégrés (les assignations augmentées et multiples, cependant, ne sont pas atomiques).

La plupart du temps, cependant, ce n’est pas une bonne idée de se fier à l’atomicité. Vous pourriez avoir affaire à une instance d’une classe codée par l’utilisateur plutôt qu’à un type intégré, de sorte qu’il pourrait y avoir des appels implicites au code Python, rendant les hypothèses d’atomicité injustifiées.

S’appuyer sur l’atomicité dépendante de l’implémentation peut verrouiller votre code dans une implémentation spécifique, ce qui peut gêner de futures mises à jour. Il est préférable d’utiliser les fonctions de synchronisation décrites dans la suite de ce chapitre, plutôt que de se fier aux hypothèses d’atomicité.

Python offre le multithreading en deux versions. Un module plus ancien et de niveau inférieur, _thread (nommé thread dans la v2), a une fonctionnalité de bas niveau et n’est pas recommandé pour une utilisation directe dans votre code.

Le module de niveau supérieur est threading, et est construit au-dessus de _thread, et c’est celui recommandé.

Le principal problème de conception dans les systèmes multithreading est de savoir comment coordonner au mieux plusieurs threads. Le module threading fournit plusieurs objets de synchronisation.

Alternativement, le module queue est très utile pour la synchronisation des threads, car il fournit des types de file d’attente synchronisés et sûrs, pratiques pour la communication et la coordination entre les threads.

Le package concurrent fournit une interface unifiée pour la communication et la coordination qui peut être implémentée par des pools de threads ou de processus.

Le module de Threading

Le module threading fournit la fonctionnalité multithreading. L’approche du threading dans Python est similaire à celle de Java, mais les verrous et les conditions sont modélisés comme des objets séparés. (en Java, une telle fonctionnalité fait partie de chaque objet), et les threads ne peuvent pas être contrôlés directement de l’extérieur (donc pas de priorités, de groupes, de destruction ou d’arrêt). Toutes les méthodes d’objets fournis par le module threading sont atomiques.

Le module threading fournit des classes permettant de manipuler les threads, comme par exemple: Thread, Condition, Event, Lock, RLock, Semaphore, Bounded Semaphore, et Timer (et, en v3 seulement, Barrier). Le module threading fournit également des fonctions, y compris :

  • active_count(): Retourne un nombre entier; le nombre d’objets Thread actuellement actifs (pas ceux qui sont terminés ou qui n’ont pas encore commencé).
  • current_thread(): Retourne un objet Thread pour le thread appelant. Si le thread appelant n’a pas été créé par le module threading, la fonction current_thread crée et renvoie un objet Thread semi-modèle avec une fonctionnalité limitée.
  • enumerate(): Retourne une liste de tous les objets Thread actuellement actifs (pas ceux qui sont terminés ou qui n’ont pas encore commencé).
  • stack_size([taille]): Retourne la taille de la pile, en octets, utilisée pour les nouveaux threads ; 0 signifie “la taille par défaut du système”.
    Lorsque vous passez la taille en paramètre, c’est ce qui sera utilisé pour les nouveaux threads créés par la suite (sur les plates-formes qui permettent de définir la taille de la pile des threads) ; les valeurs acceptables pour la taille sont soumises à des contraintes spécifiques à la plate-forme, comme être au moins 32768 (ou un minimum encore plus élevé sur certaines plates-formes) et (sur certaines plates-formes) étant un multiple de 4096.
    Passer 0 comme valeur de la taille de la pile est toujours acceptable et signifie “utiliser la taille par défaut du système”. Lorsque vous passez une valeur de taille qui n’est pas acceptable sur la plate-forme courante, stack_size soulève une exception ValueError.

Les objets Thread

Une instance de Thread t modélise un thread. Vous pouvez passer en argument une fonction pour être utilisée comme argument principal de t lorsque vous créez t, ou vous pouvez sous-classer un objet Thread et écraser la méthode run (vous pouvez aussi écraser __init__ mais vous ne devriez pas écraser d’autres méthodes).

t n’est pas encore prêt à être exécuté lorsque vous le créez ; pour rendre t prêt (actif), appelez la méthode start() avec l’instruction t.start(). Une fois que t est actif, il se termine lorsque sa fonction principale se termine, soit normalement, soit en propageant une exception.

Un thread t peut être un démon, ce qui signifie que l’interpréteur Python peut se fermer même si t est toujours actif, alors qu’un thread normal (non démon) maintient Python en vie jusqu’à ce que le thread se termine. La classe Thread fournit le constructeur, les propriétés et les méthodes suivantes :

  • Thread: class Thread(name=None,target=None,args=(),kwargs={})Toujours appelle Thread avec des arguments nommés : le nombre et l’ordre des paramètres peuvent changer à l’avenir, mais les noms des paramètres sont garantis de rester. Lorsque vous instanciez la classe Thread elle-même, passez target en paramètre: t.run appelle target(*args,**kwargs). Lorsque vous étendez Thread et écrasez la méthode run, ne passez pas le paramètre target. Dans les deux cas, l’exécution ne commence que lorsque vous appelez t.start(). Le paramètre name représente le nom de t. Si le nom est None, Thread génère un nom unique pour t. Si une classe fille T de Thread écrase __init__, T.__init__ doit appeler Thread.__init__ sur self avant toute autre méthode Thread.
  • daemon: t.daemon
    daemon est une propriété, elle est égale à True quand t est un daemon (c’est-à-dire que le processus peut se terminer même si t est toujours actif ; une telle terminaison termine aussi t) ; sinon, daemon est False. Initialement, t est un démon si et seulement si le thread qui crée t est un démon. Vous pouvez assigner à t.daemon seulement avant t.start ; assigner une valeur à t.daemon définit t pour être un daemon si, et seulement si, vous assignez une valeur vraie.
  • is_alive: t.is_alive()
    Renvoie Vrai lorsque t est actif (c’est-à-dire lorsque t.start a été exécuté et que t.run n’est pas encore terminé). Sinon, is_alive retourne False.
  • name: t.name
    name est une propriété retournant le nom de t ; assigner un nom change le nom de t (le nom existe pour vous aider à déboguer ; le nom n’a pas besoin d’être unique parmi les threads).
  • join: t.join(timeout=None)
    Suspend le thread appellant (qui ne doit pas être t) jusqu’à ce que t se termine (lorsque t est déjà terminé, le thread appellant n’est pas suspendu). Vous ne pouvez appeler t.join qu’après t.start. Il n’y a pas de problème si vous l’appelez plus d’une fois.
  • run: t.run()
    run est la méthode qui exécute la fonction principale de t. Les sous-classes de Thread peuvent écraser l’exécution. A moins qu’elle ne soit écrasée, run appelle la cible appelable passée en paramètre lors de la création de t. N’appelez pas t.run directement; appeler t.run est le travail de t.start!
  • start: t.start()
    start() rend t actif et fait en sorte que t.run s’exécute dans un thread séparé. Vous ne devez appeler t.start qu’une seule fois pour n’importe quel objet thread t ; si vous l’appelez à nouveau, cela soulève une exception.

Objets de synchronisation de threads

Le module threading fournit plusieurs primitives de synchronisation, des types qui permettent aux threads de communiquer et de coordonner. Chaque type primitif a des utilisations spécialisées.

Vous n’avez peut-être pas besoin des primitives de synchronisation de threads.

Tant que vous évitez d’avoir des variables globales de types différents de queue (file d’attente) qui changent de valeurs, et auxquelles plusieurs threads accèdent, vous pouvez souvent fournir toute la coordination dont vous avez besoin, et peuvent donc être exécutés simultanément.

Le concept de “Threaded Program Architecture” montre comment utiliser les objets Queue pour donner à vos programmes multithreads des architectures simples et efficaces, souvent sans avoir besoin d’utiliser explicitement des primitives de synchronisation.

Paramètres de temporisation (timeouts)

Les primitives de synchronisation Condition et Event fournissent des méthodes d’attente(wait) qui acceptent un argument de temporisation optionnel. La méthode joint() d’un objet Thread accepte également un argument de timeout optionnel, tout comme les méthodes d’acquisition de serrures.

Un argument de timeout peut être None (par défaut) pour obtenir un comportement de blocage normal (le thread appelant est suspendu et attend jusqu’à ce que la condition souhaitée soit remplie).

Lorsqu’il est différent de None, un argument de timeout est une valeur à virgule flottante qui indique un intervalle de temps en secondes (le timeout peut avoir une partie fractionnaire, donc il peut indiquer n’importe quel intervalle de temps, même très court).

Si le délai d’attente (timeout) s’écoule, le thread appelant redevient prêt, même si la condition désirée n’est pas remplie ; dans ce cas, la méthode d’attente retourne False (sinon, la méthode retourne True).

Le timeout vous permet de concevoir des systèmes capables de surmonter des anomalies occasionnelles dans quelques threads, et donc plus robustes. Cependant, l’utilisation du timeout peut ralentir votre programme : lorsque cela est important, assurez-vous de mesurer la vitesse de votre code avec précision.

Objets Lock et RLock

Les objets Lock et RLock fournissent les trois mêmes méthodes. Voici les signatures et la sémantique d’une instance L de Lock :

  • acquire: L.acquire(blocking=True)
    Lorsque blocking vaut True, la méthode acquire verrouille L. Lorsque L est déjà verrouillé, le thread appelant est suspendu et attend que L soit déverrouillé, puis le verrouille. Même lorsque le thread appelant était le dernier qui a verrouillé L, il suspend et attend qu’un autre thread libère L. Lorsque blocking vaut False et que L est déverrouillé, la méthode acquire verrouille L et renvoie True. Lorsque blocking vaut False et que L est verrouillé, la méthode acquire n’affecte pas L et renvoie False.
  • locked: L.locked()
    Renvoie True lorsque L est verrouillé ; sinon, retourne False.
  • release: L.release()
    Déverrouille L, qui doit être déja verrouillé. Quand L est verrouillé, n’importe quel thread peut appeler L.release, non pas seulement le thread qui a verrouillé L. Quand plus d’un thread est bloqué sur L (i.e., a appelé L.acquire, a trouvé L verrouillé, et attend que L soit déverrouillé), release réveille un thread arbitraire attendant L. Le thread appelant la méthode release n’est pas suspendu : il reste prêt et continue à s’exécuter.

La sémantique d’un objet RLock r est souvent plus pratique (sauf dans les architectures particulières où vous avez besoin d’autres threads pour pouvoir libérer les verrous qu’un différent thread a acquis).

RLock est un verrou de rentrée, ce qui signifie que, lorsque r est verrouillé, il garde la trace du thread propriétaire (c’est-à-dire le thread qui l’a verrouillé – ce qui, pour un RLock, est aussi le seul thread qui peut le libérer).

Le thread propriétaire peut appeler r.acquire à nouveau sans blocage ; r incrémente simplement un comptage interne. Dans une situation similaire impliquant un objet Lock, le thread se bloquerait jusqu’à ce qu’un autre thread libère le lock. Par exemple, considérez l’extrait de code suivant :

verrou = threading.RLock()
etat_global = []
def fonction_recursive(objet, args):
   with verrou : # acquiert verrou, garantit le déblocage à la fin.
      ... changer etat_global ...
      if plus_de_changements_necessaires(etat_global):
         fonction_recursive(autre, args)

 

Si lock était une instance de threading.Lock, récursive_function bloquerait son thread appelant lorsqu’il s’appelle récursivement : le avec instruction, trouvant que le lock a déjà été acquis (même si cela a été fait par le même thread), bloquerait et attendrait…..et attendrait…..

Avec un objet threading.RLock, aucun problème de ce type ne se produit : dans ce cas, puisque verrou a déjà été acquis par le même thread, lors d’une nouvelle acquisition, elle ne fait qu’augmenter son compte interne et procède.

Un objet RLock r n’est déverrouillé que lorsqu’il a été libéré autant de fois qu’il a été acquis. Un RLock est utile pour assurer un accès exclusif à un objet lorsque les méthodes de l’objet s’appellent mutuellement ; chaque méthode peut acquérir au début, et libérer à la fin, la même instance de RLock.

La clause try/finally est un moyen de s’assurer qu’un verrou est effectivement libéré. Une instruction with est généralement mieux : tous les verrous, conditions et sémaphores sont des gestionnaires de contexte, donc une instance de ces types peut être utilisée directement dans une clause pour l’acquérir (implicitement avec blocage) et s’assurer qu’elle est libérée à la fin du bloc with.

Objets Condition

Un objet Condition c enveloppe un objet Lock ou RLock L. La classe Condition dispose du constructeur et des méthodes suivantes:

  • Condition: class Condition(lock=None)
    Crée et renvoie un nouvel objet Condition c avec le verrouillage L réglé sur lock. Si lock vaut None, L est défini sur un objet RLock nouvellement créé.
  • acquire, release: c.acquire(blocking=1) c.release()
    Ces méthodes appellent simplement les méthodes correspondantes de L. Un thread ne doit jamais appeler une autre méthode sur c à moins que le thread ne tienne le verrou L.
  • notify,notify_all: c.notify() c.notify_all()
    notify réveille l’un des threads en attente sur c. Le thread appelant doit maintenir L avant d’appeler c.notify(), et notify ne libère pas L. Le thread réveillé ne devient pas prêt jusqu’à ce qu’il puisse acquérir L à nouveau.
    Par conséquent, le thread appelant appelle normalement release() après avoir appelé notify. notify_all est comme notify mais réveille tous les threads en attente, pas seulement un.
  • wait: c.wait(timeout=None)
    wait libère L, puis suspend le thread appelant jusqu’à ce que d’autres threads appellent notify ou notify_all sur c. Le thread appelant doit maintenir L avant d’appeler c.wait().
    Après qu’un thread se réveille, soit par notification ou par timeout, le thread devient prêt lorsqu’il acquiert à nouveau L. Quand wait retourne True (ce qui signifie qu’il est sorti normalement, pas par timeout), le thread appelant maintient toujours L à nouveau.

Habituellement, un objet Condition c régule l’accès à certain état global s partagé entre les threads. Lorsqu’un thread doit attendre que s change, le thread s’enroule :

with c:
   while not etat_ok(s):
      c.wait()
   faire_quelque_chose_avec_etat(s)

Pendant ce temps, chaque thread qui modifie s appels notify (ou notify_all si elle a besoin de réveiller tous les threads en attente, pas seulement un) à chaque fois que s change :

with c:
   faire_quelque_chose_moifiant_etat(s)
   c.notify() # or, c.notify_all()
# pas besoin d'appeler c.release(), quitter `with le fait intrinsèquement

Vous avez toujours besoin d’acquérir et de libérer c autour de chaque utilisation des méthodes de c : le faire via une instruction with rend l’utilisation des instances de la classe Condition moins sujette aux erreurs.

 

Objets Event

Les objets événements permettent de suspendre un nombre quelconque de threads et d’attendre. Tous les threads en attente sur l’objet Event e deviennent prêts lorsque n’importe quel autre thread appelle e.set().

L’objet e a un drapeau qui enregistre si l’événement s’est produit; il vaut initialement false lorsque e est créé. Event est donc un peu comme une condition simplifiée.

Les objets d’événements sont utiles pour signaler des changements ponctuels, mais fragiles pour un usage plus général ; en particulier, se baser sur les appels e.clear() est sujet aux erreurs. La classe Event expose les méthodes suivantes :

  • Event: class Event()
    Crée et renvoie un nouvel objet Event e, avec son flag réglé sur False.
  • clear: e.clear()
    Place le flag de e sur False.
  • is_set: e.is_set()
    Renvoie la valeur du flag de e, True ou False.
  • set: e.set()
    Définit le flag de e à True. Tous les threads en attente sur e, s’il y en a, deviennent prêts à fonctionner.
  • wait: e.wait(timeout=None)
    Si le flag de e vaut True, wait renvoie immédiatement. Sinon, wait suspend le thread appelant jusqu’à ce que d’autres appels de threads soient mis en place.

Objets Semaphore

Les sémaphores (syntaxe: Semaphore(n=1)), aussi appelés sémaphores de comptage, sont une généralisation des verrous. L’état d’un Lock peut être vu comme True ou False ; l’état d’un Sémaphore s est un nombre compris entre 0 et un certain n défini lors de la création de s (les deux bornes incluses).

Les sémaphores peuvent être utiles pour gérer un pool fixe de ressources (par exemple, 4 imprimantes ou 20 sockets), bien qu’il soit souvent plus robuste d’utiliser les files d’attente à de telles fins.

La classe BoundedSemaphore (ex: BoundedSemaphore(n=1)) est très similaire, mais soulève l’exception ValueError si l’état devient supérieur à la valeur initiale : dans de nombreux cas, un tel comportement peut être un indicateur utile d’un bogue de codage.

Un objet sémaphore s dispose des méthodes suivantes :

  • acquire: s.acquire(blocking=True)
    Lorsque l’état de s est >0, la méthode acquire décremente l’état par 1 et retourne True. Lorsque l’état de s est 0 et que blocking vaut True, acquire suspend le thread appelant et attend jusqu’à ce qu’un thread appelle s.release. Lorsque l’état de s est 0 et que le blocking vaut False, acquire immédiatement retourne False.
  • release: s.release()
    Quand l’état de s est >0, ou quand l’état est 0 mais qu’aucun thread n’attend sur s, release incrémente l’état de 1. Quand l’état de s est 0 et que certains threads sont en attente sur s, release laisse l’état de s à 0 et réveille un thread arbitraire parmi les threads en attente. Le thread qui appelle release ne se suspend pas ; il reste prêt et continue à s’exécuter normalement.

Objets Timer

Un objet Timer appelle un callable spécifié, dans un thread nouvellement créé, après un délai donné. La classe Timer dispose d’un constructeur et les méthodes suivantes :

  • Timer: class Timer (interval, callable,args=(),kwargs={})
    Rend un objet t, qui appelle callable, intervalle de secondes après le démarrage (le paramètre interval est un nombre à virgule flottante et peut inclure une partie fractionnaire).
  • cancel: cancel()
    t.cancel() arrête le timer et annule l’exécution de son action, tant que t est toujours en attente (n’a pas encore appelé son callable) lorsque vous appelez cancel.
  • start: start()
    t.start() démarre t.

Timer étend Thread et ajoute la fonction d’attributs, l’intervalle, les args et les kwargs.
Un Timer est “one-shot”, c’est-à-dire qu’il ne peut être appelé qu’une seule fois.

Pour appeler périodiquement, toutes les secondes d’intervalle, voici une recette simple :

class Periodique(threading.Timer):
   def __init__(self, interval, callable, args=(), kwargs={}):
      self.callable = callable
      threading.Timer.__init__(self, interval, self._f, args, kwargs)

   def _f(self, *args, **kwargs):
      Periodique(self.interval, self.callable, args, kwargs).start()
      self.callable(*args, **kwargs)

 

Objets Barrier (Python v3 seulement)

Un objet Barrier est une primitive de synchronisation permettant à un certain nombre de threads d’attendre qu’ils aient tous atteint un certain point dans leur exécution, avant qu’ils ne reprennent tous.

Spécifiquement, quand un thread appelle b.wait(), il se bloque jusqu’à ce que le nombre spécifié de threads ait fait le même appel sur b ; à ce moment-là, tous les threads bloqués sur b sont libérés.

La classe Barrier dispose d’un constructeur, les méthodes et les propriétés suivantes :

  • Barrier: class Barrier(num_threads, action=None, timeout=None)
    La fonction action est appelable sans arguments : si vous passez cet argument, il s’exécute sur n’importe lequel des threads bloqués lorsqu’ils sont tous débloqués.
  • abort: abort()
    b.abort() place Barrier b dans l’état brisé, ce qui signifie que tout thread actuellement en attente reprend avec une exception threading.BrokenBarrierException (la même exception est également levée sur tout appel subséquent à b.wait()). Il s’agit d’une action d’urgence généralement utilisée lorsqu’un thread en attente souffre d’une terminaison anormale, afin d’éviter de bloquer l’ensemble du programme.
  • broken: broken
    True quand b est dans l’état brisé ; sinon, False.
  • n_waiting: n_waiting
    Nombre de threads actuellement en attente de b.
  • parties: parties
    La valeur passée en tant que num_threads dans le constructeur de b.
  • reset: reset()
    Retourne b à l’état initial, vide, non brisé ; tout thread actuellement en attente de b, cependant, reprend avec une exception threading.BrokenBarrierException.
  • wait: wait()
    Le premier b.parties – 1 threads appelant le bloc b.wait() ; lorsque le nombre de threads bloqués sur b.parties – 1 et un autre thread appelle b.wait(), tous les threads bloqués sur b reprennent. b.wait() renvoie un int à chaque thread repris, tous distincts et dans un ordre non spécifié ; les threads peuvent utiliser cette valeur de retour pour déterminer lequel doit faire quoi ensuite (bien que l’action de dépassement dans le constructeur d’un objet Barrier soit plus simple et souvent suffisante).

Stockage local des données des threads

Le module threading fournit la classe local, qu’un thread peut utiliser pour obtenir un thread-local storage (TLS), également connu sous le nom de données par thread.

Une instance L de local a des attributs nommés arbitraires que vous pouvez définir et obtenir, et les stocke dans un dictionnaire L.__dict__ auquel vous pouvez également accéder.

L est entièrement thread-safe, ce qui signifie qu’il n’y a pas de problème si plusieurs threads sont définis simultanément et obtiennent des attributs sur L. Plus important encore, chaque thread qui accède à L voit un ensemble disjoint d’attributs, et tout changement apporté à un thread n’a aucun effet sur les autres threads. Par exemple :

import threading
L = threading.local()
print('dans le thread principal, on définit la valeur zop à 42')
L.zop = 42
def targ():
   print('dans un sous-thread, définir zop à 23')
   L.zop = 23
   print('dans le sous-thread, zop est maintenant', L.zop)
t = threading.Thread(target=targ)
t.start()
t.join()
print('dans le thread principal, zop est maintenant', L.zop)
# prints:
# dans le thread principal, on définit la valeur zop à 42
# dans un sous-thread, définir zop à 23
# dans le sous-thread, zop est maintenant 23
# dans le thread principal, zop est maintenant 42

TLS facilite l’écriture de code destiné à s’exécuter dans plusieurs threads, puisque vous pouvez utiliser le même espace de noms (une instance de threading.local) dans plusieurs threads sans que les threads séparés interfèrent les uns avec les autres.

Le module queue

Le module queue (nommé Queue dans la version 2) fournit des types de file d’attente supportant l’accès multithread, avec une classe principale, deux sous-classes et deux classes d’exception :

  • Queue: class Queue(maxsize=0)
    Queue, la classe principale du module queue, implémente une file d’attente First-In, First-Out (FIFO) (l’élément récupéré à chaque fois est celui qui a été ajouté le plus tôt).
    Lorsque maxsize est >0, la nouvelle instance Queue q est considérée comme pleine lorsque le nombre des éléments de q est maxsize. Un thread insérant un élément avec l’option block, quand q est plein, est suspendu jusqu’à ce qu’un autre thread extrait un élément. Quand maxsize est <=0, q n’est jamais considéré comme plein, et n’est limité en taille que par la mémoire disponible, comme les conteneurs Python normaux.
  • LifoQueue: class LifoQueue(maxsize=0)
    LifoQueue est une sous-classe de Queue ; la seule différence est que LifoQueue est Last-In, First-Out (LIFO), ce qui signifie que l’élément récupéré à chaque fois est celui qui a été ajouté le plus récemment.
  • PriorityQueue: class PriorityQueue(maxsize=0)
    PriorityQueueue est une sous-classe de Queue ; la seule différence est que PriorityQueue implémente une file d’attente prioritaire – l’élément récupéré à chaque fois est le plus petit actuellement dans la file d’attente. Comme il n’y a pas d’argument clé, vous utilisez généralement la file d’attente, comme éléments de la file d’attente, des paires (priorité, payload), avec des valeurs basses de priorité signifiant une récupération plus précoce.
  • Empty: Empty est l’exception que q.get(False) soulève quand q is vide.
  • Full: Full est l’exception que q.put(x,False) soulève quand q est plein.

Méthodes des Instances Queue

Une instance q de la classe Queue (ou l’une de ses sous-classes) fournit les méthodes suivantes, toutes sécurisées et atomiques :

  • empty: q.empty()
    Renvoie True si q est vide ; sinon, False.
  • full: q.full()
    Renvoie True si q est plein ; sinon, False.
  • get, get_nowait: q.get(block=True,timeout=None)
    Lorsque le block vaut Faux, get retire et retourne un item de q si un item est disponible ; sinon, get soulève l’exception Empty. Lorsque block vaut True et que timeout vaut None, get retire et renvoie un élément de q, en suspendant le thread appelant, si nécessaire, jusqu’à ce qu’un élément soit disponible.Lorsque block vaut True et que le timeout n’est égale à None, le timeout doit être un nombre >=0 (qui peut inclure une partie fraconnaire pour spécifier une fraction de seconde), et get attend une durée ne dépassant pas le timeout (si aucun élément n’est encore disponible d’ici là, get soulève l’exception Empty).
    q.get_nowait() est comme q.get(False), qui est aussi comme q.get(timeout=0.0). get supprime et retourne les éléments dans le même ordre que lorsqu’ils sont insérés (FIFO), si q est une instance directe de Queue elle-même ; LIFO, si q est une instance de LifoQueueue ; le plus petit en premier, si q est une instance de PriorityQueueue.
  • put, put_nowait: q.put(item,block=True,timeout=None)
    Lorsque block vaut False, put ajoute item à q si q n’est pas plein ; sinon, put soulève l’exception Full. Lorsque block vaut True et que timeout vaut None, put ajoute item à q, en suspendant le thread appelant, si nécessaire, jusqu’à ce que q ne soit pas plein. Lorsque block vaut True et que timeout n’est pas None, timeout doit être un nombre >=0 (qui peut inclure une fraction de seconde), et put attend au maximum la durée spécifiée par timeout (si q est encore plein, put soulève l’exception Full). q.put_nowait(item) est comme q.put(item,False), aussi comme q.put(item,timeout=0.0).
  • qsize: q.qsize()
    Renvoie le nombre d’éléments qui sont actuellement dans q.

De plus, q maintient un comptage interne et caché des tâches inachevées, qui commence à zéro. Chaque appel à get() incrémente le compteur d’une unité. Pour décrémenter le compte par un, quand un thread a fini de traiter une tâche, il appelle q.task_done().

Pour synchroniser sur “toutes les tâches effectuées”, appelez q.join(): join continue l’exécution du thread appelant lorsque le compteur des tâches inachevées est à zéro ; lorsque le compteur est différent de zéro, q.join() bloque le thread appelant, et débloque plus tard, lorsque le compteur va à zéro.

Vous n’avez pas besoin d’utiliser join et task_done si vous préférez coordonner les threads d’autres manières, mais join et task_done fournissent une approche simple et utile pour coordonner les systèmes de threads en utilisant une file d’attente Queue.

 

LAISSER UN COMMENTAIRE

Please enter your comment!
Please enter your name here