Python Language
Python samtidighet
Sök…
Anmärkningar
Python-utvecklarna såg till att API mellan threading
och multiprocessing
liknar så att det är lättare att växla mellan de två varianterna för programmerare.
Trådmodulen
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()
I vissa implementationer av Python, såsom CPython, uppnås inte sann parallellitet med hjälp av trådar på grund av att använda det som kallas GIL, eller G lobal I nterpreter L ock.
Här är en utmärkt översikt av Python samtidighet:
Python samtidighet av David Beazley (YouTube)
Multiprocesseringsmodulen
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()
Här utförs varje funktion i en ny process. Eftersom en ny instans av Python VM kör koden finns det ingen GIL
och du får parallellitet på flera kärnor.
De Process.start
metoden lanserar denna nya process och kör funktionen passerade i target
argumentet med argument args
. Process.join
metoden väntar på slutet av exekveringen av processerna p1
och p2
.
De nya processerna lanseras annorlunda beroende på versionen av python och plattformen som koden körs på, t.ex.
- Windows använder
spawn
att skapa den nya processen. - Med unix-system och version tidigare än 3.3 skapas processerna med en
fork
.
Observera att den här metoden inte respekterar POSIX-användningen av gaffel och därmed leder till oväntat beteende, särskilt när du interagerar med andra multiprocesserbibliotek. - Med unix-system och version 3.4+ kan du välja att starta de nya processerna med antingen
fork
,forkserver
ellerspawn
medmultiprocessing.set_start_method
i början av programmet.forkserver
ochspawn
metoder är långsammare än forking men undviker oväntat beteende.
POSIX gaffelanvändning :
Efter en gaffel i ett multithread-program kan barnet säkert bara ringa async-signal-säkra funktioner tills det samtalet körs.
( se )
Med gaffel kommer en ny process att startas med exakt samma tillstånd för all den aktuella mutex men bara MainThread
kommer att startas. Detta är osäkert eftersom det kan leda till tävlingsförhållanden, t.ex.
- Om du använder ett
Lock
iMainThread
ochMainThread
det till en annan tråd som antas låsa det vid någon tidpunkt. Omfork
sker samtidigt börjar den nya processen med ett låst lås som aldrig kommer att släppas eftersom den andra tråden inte finns i den nya processen.
Egentligen borde denna typ av beteende inte uppstå i ren python eftersom multiprocessing
hanterar det ordentligt, men om du interagerar med andra bibliotek kan denna typ av uppträdande inträffa, vilket kan leda till att ditt system kraschar (till exempel med numpy / accelererad på macOS).
Vidarebefordra data mellan multiprocesseringsprocesser
Eftersom data är känsliga när de hanteras mellan två trådar (tänk samtidigt läsning och samtidig skrivning kan komma i konflikt med varandra och orsaka rasförhållanden) gjordes en uppsättning unika objekt för att underlätta överföring av data fram och tillbaka mellan trådar. Alla verkliga atomoperationer kan användas mellan trådar, men det är alltid säkert att hålla sig till kö.
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 flesta kommer att föreslå att när du använder kö, alltid ska du placera ködata i ett försök: förutom: blockera istället för att använda tomt. Men för applikationer där det inte spelar någon roll om du hoppar över en skanningscykel (data kan placeras i kön medan det vänder tillstånd från queue.Empty==True
till queue.Empty==False
) är det vanligtvis bättre att läsa och skrivåtkomst i det jag kallar ett Iftry-block, eftersom ett "if" -uttalande är tekniskt mer performant än att fånga undantaget.
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