Python Language
Samenloop van Python
Zoeken…
Opmerkingen
De Python-ontwikkelaars hebben ervoor gezorgd dat de API tussen threading
en multiprocessing
vergelijkbaar is, zodat het schakelen tussen de twee varianten eenvoudiger is voor programmeurs.
De threading-module
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()
In bepaalde implementaties van Python zoals CPython, wordt ware parallelliteit niet bereikt met behulp van threads vanwege het gebruik van wat bekend staat als de GIL of G lobal I nterpreter L ock.
Hier is een uitstekend overzicht van de gelijktijdigheid van Python:
Python concurrency door David Beazley (YouTube)
De multiprocessing-module
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 wordt elke functie in een nieuw proces uitgevoerd. Aangezien een nieuwe instantie van Python VM de code uitvoert, is er geen GIL
en krijg je parallellisme op meerdere cores.
De Process.start
methode lanceert dit nieuwe proces en uitvoeren van de functie doorgegeven in het target
ruzie met de argumenten args
. De methode Process.join
wacht op het einde van de uitvoering van processen p1
en p2
.
De nieuwe processen worden anders gestart, afhankelijk van de versie van python en de plateform waarop de code wordt uitgevoerd, bijvoorbeeld :
- Windows gebruikt
spawn
om het nieuwe proces te maken. - Met unix-systemen en versies eerder dan 3.3 worden de processen gemaakt met behulp van een
fork
.
Merk op dat deze methode het POSIX-gebruik van vork niet respecteert en dus tot onverwacht gedrag leidt, vooral bij interactie met andere multiprocessing-bibliotheken. - Met het unix-systeem en versie 3.4+, kunt u ervoor kiezen om de nieuwe processen te starten met
fork
,forkserver
ofspawn
metmultiprocessing.set_start_method
aan het begin van uw programma.forkserver
enspawn
methoden zijn langzamer dan forking, maar vermijd onverwacht gedrag.
POSIX vork gebruik :
Na een vork in een multithreaded programma, kan het kind veilig alleen async-signaal-veilige functies aanroepen totdat het execve roept.
( zie )
Met behulp van vork wordt een nieuw proces gestart met exact dezelfde status voor alle huidige mutex, maar alleen de MainThread
wordt gestart. Dit is onveilig omdat dit kan leiden tot raceomstandigheden, bijvoorbeeld :
- Als u een
Lock
inMainThread
en deze doorgeeft aan een andere thread waarvan u veronderstelt dat deze op een bepaald moment wordt vergrendeld. Als defork
tegelijkertijd optreedt, begint het nieuwe proces met een vergrendelde vergrendeling die nooit wordt vrijgegeven, omdat de tweede thread niet bestaat in dit nieuwe proces.
Eigenlijk zou dit soort gedrag niet in pure python moeten voorkomen, omdat multiprocessing
het goed afhandelt, maar als je in interactie bent met een andere bibliotheek, kan dit soort gedrag optreden, wat kan leiden tot een crash van je systeem (bijvoorbeeld met numpy / accelerated op macOS).
Gegevens doorgeven tussen multiprocessing-processen
Omdat gegevens gevoelig zijn wanneer ze tussen twee threads worden behandeld (denk dat gelijktijdig lezen en gelijktijdig schrijven met elkaar kunnen conflicteren, waardoor race-omstandigheden kunnen ontstaan), zijn een aantal unieke objecten gemaakt om het heen en weer doorgeven van gegevens tussen threads mogelijk te maken. Elke echt atomaire bewerking kan tussen threads worden gebruikt, maar het is altijd veilig om bij Wachtrij te blijven.
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
De meeste mensen zullen suggereren om bij het gebruik van de wachtrij de wachtrijgegevens altijd in een poging te plaatsen: behalve: blokkeren in plaats van leeg gebruiken. Voor toepassingen waarbij het niet uitmaakt of u een queue.Empty==True
overslaat (gegevens kunnen in de wachtrij worden geplaatst terwijl de queue.Empty==True
uit de wachtrij wordt queue.Empty==True
aan queue.Empty==False
) is meestal beter om te lezen en schrijf toegang in wat ik een Iftry-blok noem, omdat een 'if'-instructie technisch beter presteert dan de uitzondering opvangt.
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