Python Language
Python Parallelität
Suche…
Bemerkungen
Die Python-Entwickler stellten sicher, dass die API zwischen threading
und multiprocessing
ähnlich ist, sodass der Wechsel zwischen den beiden Varianten für Programmierer einfacher ist.
Das Einfädelmodul
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()
Bei bestimmten Implementierungen von Python wie CPython ist wahr Parallelität nicht wegen der Verwendung , was als die GIL bekannt ist die Verwendung von Threads erreicht, oder G LOBALE I nterpreter L ock.
Hier ist ein hervorragender Überblick über die Parallelität von Python:
Python-Parallelität von David Beazley (YouTube)
Das Multiprocessing-Modul
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()
Hier wird jede Funktion in einem neuen Prozess ausgeführt. Da eine neue Instanz von Python VM den Code GIL
, gibt es keine GIL
und Sie können Parallelität auf mehreren Kernen ausführen.
Die Process.start
Methode startet diesen neuen Prozess und führen Sie die Funktion in dem übergebenen target
Argumente mit den Argumenten args
. Die Process.join
Methode wartet auf das Ende der Ausführung der Prozesse p1
und p2
.
Die neuen Prozesse werden gestartet unterschiedlich , je nach Version von Python und der entsprechenden Plattform , auf die der Code zum Beispiel ausgeführt wird :
- Windows verwendet
spawn
, um den neuen Prozess zu erstellen. - Bei Unix-Systemen und Versionen vor 3.3 werden die Prozesse mithilfe einer
fork
.
Beachten Sie, dass diese Methode die POSIX-Verwendung von Fork nicht beachtet und daher zu unerwartetem Verhalten führt, insbesondere wenn Sie mit anderen Multiprocessing-Bibliotheken interagieren. - Mit Unix-System und Version 3.4 und höher können Sie die neuen Prozesse entweder mit
fork
,forkserver
oderspawn
Verwendung vonmultiprocessing.set_start_method
am Anfang Ihres Programms starten.forkserver
undspawn
methoden sind langsamer als forking, vermeiden jedoch unerwartetes Verhalten.
POSIX-Gabelverwendung :
Nach einer Abzweigung in einem Multithread-Programm kann das Kind nur sicherheitsgerichtete asynchrone Funktionen aufrufen, bis es ausgeführt wird.
( siehe )
Mit Fork wird ein neuer Prozess mit demselben Status für den gesamten aktuellen Mutex gestartet, aber nur der MainThread
wird gestartet. Dies ist unsicher, da dies zu Rennbedingungen führen kann, z .
- Wenn Sie eine
Lock
inMainThread
und sie an einen anderen Thread übergeben, der sie an einem bestimmten Punkt sperren soll. Wenn diefork
gleichzeitig occures, wird das neue Verfahren mit einem gesperrten Schloss beginnen, die nie als der zweite Thread freigegeben wird nicht in diesem neuen Verfahren existiert.
Eigentlich sollte diese Art von Verhalten nicht in reinem Python auftreten, da multiprocessing
dies richtig handhabt. Wenn Sie jedoch mit anderen Bibliotheken interagieren, kann diese Art von Verhalten auftreten, was zum Absturz Ihres Systems führen kann (z. B. mit numpy / beschleunigt auf macOS).
Übergabe von Daten zwischen Multiprozessoren
Da die Daten zwischen zwei Threads vertraulich behandelt werden (wenn man annimmt, dass gleichzeitiges Lesen und gleichzeitiges Schreiben Konflikte verursachen können, was zu Race-Bedingungen führt), wurde eine Reihe von eindeutigen Objekten erstellt, um das Weiterleiten von Daten zwischen Threads zu erleichtern. Jede wirklich atomare Operation kann zwischen Threads verwendet werden, aber es ist immer sicher, bei Queue zu bleiben.
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
Die meisten Leute schlagen vor, bei der Verwendung der Warteschlange immer die Warteschlangendaten in einem try: zu platzieren, außer: blockieren statt leer zu verwenden. Bei Anwendungen, bei denen es nicht darauf ankommt, einen Scan-Zyklus zu überspringen (Daten können in die Warteschlange gestellt werden, während der queue.Empty==True
aus der Warteschlange queue.Empty==True
in die Warteschlange. queue.Empty==False
), ist es in der Regel besser, read zu platzieren und Schreibzugriff auf das, was ich als Iftry-Block bezeichne, da eine 'if'-Anweisung technisch performanter ist als die Ausnahme.
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