Recherche…


Remarques

Les développeurs Python se sont assurés que l’API entre les threading et le multiprocessing est similaire, de sorte que la commutation entre les deux variantes est plus facile pour les programmeurs.

Le module de filetage

from __future__ import print_function
import threading
def counter(count):
    while count > 0:
        print("Count value", count)
        count -= 1
    return

t1 = threading.Thread(target=countdown,args=(10,))
t1.start()
t2 = threading.Thread(target=countdown,args=(20,))
t2.start()

Dans certaines implémentations de Python tels que CPython, le vrai parallélisme n'est pas réalisée à l' aide des fils en raison de l' utilisation de ce qui est connu comme le GIL, ou G Lobal I nterpreter L ock.

Voici un excellent aperçu de la concurrence Python:

La concurrence Python par David Beazley (YouTube)

Le module de multitraitement

from __future__ import print_function
import multiprocessing


def countdown(count):
    while count > 0:
        print("Count value", count)
        count -= 1
    return

if __name__ == "__main__":
    p1 = multiprocessing.Process(target=countdown, args=(10,))
    p1.start()

    p2 = multiprocessing.Process(target=countdown, args=(20,))
    p2.start()

    p1.join()
    p2.join()

Ici, chaque fonction est exécutée dans un nouveau processus. Comme une nouvelle instance de Python VM exécute le code, il n'y a pas de GIL et le parallélisme s'exécute sur plusieurs cœurs.

La méthode Process.start lance ce nouveau processus et exécute la fonction passée dans l'argument target avec les arguments args . La méthode Process.join attend la fin de l'exécution des processus p1 et p2 .

Les nouveaux processus sont lancés différemment selon la version de python et la plateforme sur laquelle le code est exécuté, par exemple :

  • Windows utilise spawn pour créer le nouveau processus.
  • Avec les systèmes unix et la version antérieure à la version 3.3, les processus sont créés à l'aide d'un fork .
    Notez que cette méthode ne respecte pas l'utilisation POSIX de fork et conduit donc à des comportements inattendus, notamment lors de l'interaction avec d'autres bibliothèques multiprocesseurs.
  • Avec le système unix et la version 3.4+, vous pouvez choisir de lancer les nouveaux processus avec fork , forkserver ou spawn utilisant multiprocessing.set_start_method au début de votre programme. forkserver méthodes de forkserver et de spawn sont plus lentes que les méthodes de forking mais évitent certains comportements inattendus.

Utilisation de la fourche POSIX :

Après un fork dans un programme multithread, l'enfant peut en toute sécurité appeler uniquement les fonctions async-signal-safe jusqu'à ce qu'il appelle execve.
( voir )

En utilisant fork, un nouveau processus sera lancé avec exactement le même état pour tous les mutex actuels, mais seul le MainThread sera lancé. Ceci est dangereux car cela pourrait conduire à des conditions de course, par exemple :

  • Si vous utilisez un Lock dans MainThread et le transmettez à un autre thread qui est censé le verrouiller à un moment donné. Si le fork simultanément, le nouveau processus démarrera avec un verrou verrouillé qui ne sera jamais publié car le deuxième thread n'existe pas dans ce nouveau processus.

En fait, ce type de comportement ne devrait pas se produire en python pur, car le multiprocessing gère correctement, mais si vous interagissez avec d'autres bibliothèques, ce type de comportement peut entraîner un blocage de votre système (par exemple, numpy / accéléré sur macOS).

Passer des données entre des processus multiprocessus

Les données étant sensibles lorsqu'elles sont traitées entre deux threads (pensez que les lectures simultanées et les écritures concurrentes peuvent être en conflit les unes avec les autres, provoquant des conditions de concurrence), un ensemble d'objets uniques a été créé pour faciliter la transmission des données entre les threads. Toute opération vraiment atomique peut être utilisée entre les threads, mais il est toujours prudent de s'en tenir à la file d'attente.

import multiprocessing
import queue
my_Queue=multiprocessing.Queue() 
#Creates a queue with an undefined maximum size
#this can be dangerous as the queue becomes increasingly large
#it will take a long time to copy data to/from each read/write thread

Lors de l'utilisation de la file d'attente, la plupart des utilisateurs suggèrent de toujours placer les données de la file d'attente dans un essai: sauf: bloquer au lieu d'utiliser vide. Cependant, pour les applications où il est queue.Empty==True passer un cycle d'analyse (les données peuvent être placées dans la file d'attente en retournant les états de queue.Empty==True en queue.Empty==False d' queue.Empty==False ) il est généralement préférable de lire et écrivez un accès dans ce que j'appelle un bloc Iftry, car une instruction "if" est techniquement plus performante que la capture d'une exception.

import multiprocessing
import queue
'''Import necessary Python standard libraries, multiprocessing for classes and queue for the queue exceptions it provides'''
def Queue_Iftry_Get(get_queue, default=None, use_default=False, func=None, use_func=False):
    '''This global method for the Iftry block is provided for it's reuse and 
standard functionality, the if also saves on performance as opposed to catching
 the exception, which is expencive.
        It also allows the user to specify a function for the outgoing data to use,
 and a default value to return if the function cannot return the value from the queue'''
        if get_queue.empty():
            if use_default:
                return default
        else:
            try:
                value = get_queue.get_nowait()
            except queue.Empty:
                if use_default:
                    return default
            else:
                if use_func:
                    return func(value)
                else:
                    return value
    def Queue_Iftry_Put(put_queue, value):
        '''This global method for the Iftry block is provided because of its reuse 
and 
standard functionality, the If also saves on performance as opposed to catching
 the exception, which is expensive.
        Return True if placing value in the queue was successful. Otherwise, false'''
        if put_queue.full():
            return False
        else:
            try:
                put_queue.put_nowait(value)
            except queue.Full:
                return False
            else:
                return True


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow