Python Language
Concurrence Python
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
ouspawn
utilisantmultiprocessing.set_start_method
au début de votre programme.forkserver
méthodes deforkserver
et despawn
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
dansMainThread
et le transmettez à un autre thread qui est censé le verrouiller à un moment donné. Si lefork
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