De `concurrent` module in Python
In dit artikel leggen we de concurrent module in Python uit.
Terwijl we de concepten van gelijktijdigheid en parallelisme verduidelijken, leggen we uit hoe je asynchrone verwerking implementeert met behulp van de concurrent module aan de hand van praktische voorbeelden.
YouTube Video
De concurrent module in Python
Bij het versnellen van verwerking in Python is het belangrijk om het verschil tussen gelijktijdigheid en parallelisme in gedachten te houden. De concurrent module is een belangrijk middel om asynchrone verwerking veilig en eenvoudig uit te voeren met deze verschillen in gedachten.
Het verschil tussen gelijktijdigheid en parallelisme
-
Gelijktijdigheid betekent het zo ontwerpen van een proces dat meerdere taken worden uitgevoerd door in kleine werkstukken tussen hen te wisselen. Zelfs als taken niet echt gelijktijdig worden uitgevoerd, kun je wachttijd gebruiken om het hele proces efficiënter te laten verlopen.
-
Parallelisme is een mechanisme waarbij meerdere taken fysiek gelijktijdig worden uitgevoerd. Door meerdere CPU-kernen te gebruiken, kan verwerking gelijktijdig plaatsvinden.
Beide zijn technieken om verwerking te versnellen, maar gelijktijdigheid is een ontwerpkwestie van 'hoe voort te gaan', terwijl parallelisme een uitvoeringskwestie is van 'hoe het draait', waardoor ze fundamenteel verschillend zijn.
Wat is de concurrent module?
concurrent is een standaardbibliotheek in Python die een high-level API biedt om gelijktijdigheid en parallelisme veilig en eenvoudig te beheren. Het is ontworpen zodat je je kunt richten op het uitvoeren van taken zonder je zorgen te maken over low-level bewerkingen zoals het aanmaken en beheren van threads of processen.
Rollen van ThreadPoolExecutor en ProcessPoolExecutor
De concurrent module biedt twee hoofdopties afhankelijk van de aard van de taak.
-
ThreadPoolExecutorDit is geschikt voor gelijktijdige implementaties, vooral voor taken met veel I/O-wachttijd, zoals netwerk- of bestandsbewerkingen. Door tussen taken te wisselen, wordt de wachttijd effectief benut. -
ProcessPoolExecutorDeze implementatie is gericht op parallelle verwerking en is geschikt voor CPU-intensieve taken. Het gebruikt meerdere processen om de beschikbare CPU-kernen parallel optimaal te benutten.
Een belangrijk kenmerk van de concurrent module is dus dat deze een structuur biedt waarmee je op de juiste manier kunt kiezen tussen gelijktijdigheid en parallelisme waar nodig.
Basis van ThreadPoolExecutor (voor I/O-taken)
ThreadPoolExecutor is geschikt voor I/O-gebonden taken, zoals netwerkcommunicatie en bestandsbewerkingen. Het verdeelt taken over meerdere threads, zodat de wachttijd effectief wordt benut.
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12 for future in futures:
13 print(future.result())- In dit voorbeeld worden meerdere I/O-taken die één seconde wachten gelijktijdig uitgevoerd. Door
submitte gebruiken, worden functie-aanroepen geregistreerd als asynchrone taken, en metresult()kun je wachten op voltooiing en de resultaten ophalen, waardoor je op een beknopte manier gelijktijdige verwerking die optimaal gebruikmaakt van wachttijd kunt implementeren.
Eenvoudige gelijktijdigheid met behulp van map
Als complexe controle niet nodig is, kan het gebruik van map je code beknopter maken.
1from concurrent.futures import ThreadPoolExecutor
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 results = executor.map(fetch_data, range(5))
11
12 for result in results:
13 print(result)- In dit voorbeeld worden meerdere I/O-taken gelijktijdig uitgevoerd met
ThreadPoolExecutor.map. Omdatmapresultaten in dezelfde volgorde als de invoer retourneert, kun je code schrijven die bijna op sequentiële verwerking lijkt, en gelijktijdige uitvoering is mogelijk zonder dat je je bewust hoeft te zijn van asynchrone verwerking—dit is een groot voordeel.
Basis van ProcessPoolExecutor (voor CPU-gebonden taken)
Voor zware berekeningen die de CPU volledig benutten, moet je processen gebruiken in plaats van threads. Hierdoor kun je de beperking van de Global Interpreter Lock (GIL) vermijden.
1from concurrent.futures import ProcessPoolExecutor
2
3def heavy_calculation(n):
4 # Simulate a CPU-bound task
5 total = 0
6 for i in range(10_000_000):
7 total += i * n
8 return total
9
10if __name__ == "__main__":
11 with ProcessPoolExecutor(max_workers=4) as executor:
12 results = executor.map(heavy_calculation, range(4))
13
14 for result in results:
15 print(result)In dit voorbeeld worden CPU-intensieve berekeningen parallel uitgevoerd met ProcessPoolExecutor. Omdat het aanmaken van processen vereist is, is een __main__-bescherming nodig, wat veilige parallelle verwerking over meerdere CPU-kernen mogelijk maakt.
Verwerken op voltooiingsvolgorde met behulp van as_completed
as_completed is handig wanneer je resultaten wilt afhandelen in de volgorde waarin ze voltooid zijn.
1from concurrent.futures import ThreadPoolExecutor, as_completed
2import time
3
4def fetch_data(n):
5 # Simulate an I/O-bound task
6 time.sleep(1)
7 return f"data-{n}"
8
9with ThreadPoolExecutor(max_workers=3) as executor:
10 futures = [executor.submit(fetch_data, i) for i in range(5)]
11
12 for future in as_completed(futures):
13 print(future.result())- In dit voorbeeld worden meerdere asynchrone taken gelijktijdig uitgevoerd en worden de resultaten opgehaald in de volgorde waarin ze voltooid zijn. Met
as_completedkun je snel resultaten verwerken ongeacht de takenvolgorde, waardoor het geschikt is voor voortgangsweergave of situaties waarin een sequentiële afhandeling vereist is.
Omgaan met uitzonderingen
In concurrent worden uitzonderingen opgeworpen wanneer je result() aanroept.
1from concurrent.futures import ThreadPoolExecutor
2
3def risky_task(n):
4 # Simulate a task that may fail for a specific input
5 if n == 3:
6 raise ValueError("Something went wrong")
7 return n * 2
8
9with ThreadPoolExecutor() as executor:
10 futures = [executor.submit(risky_task, i) for i in range(5)]
11
12 for future in futures:
13 try:
14 print(future.result())
15 except Exception as e:
16 print("Error:", e)- Dit voorbeeld toont aan dat zelfs als sommige taken uitzonderingen veroorzaken, de andere gewoon doorgaan en je uitzonderingen afzonderlijk kunt afhandelen bij het ophalen van de resultaten. Door gebruik te maken van de
Futurevanconcurrent, is het belangrijk dat successen en fouten in asynchrone verwerking veilig kunnen worden afgehandeld.
Richtlijnen voor het kiezen tussen threads en processen
Om gelijktijdigheid en parallelisme effectief te gebruiken, is het belangrijk om de juiste aanpak te kiezen op basis van de aard van de taak.
In de praktijk kunnen de volgende criteria je helpen beslissen.
- Voor processen met veel I/O-wachttijden, zoals communicatie of bestandsbewerkingen, gebruikt u
ThreadPoolExecutor. - Voor CPU-intensieve, zware berekeningen, gebruik
ProcessPoolExecutor. - Als er veel eenvoudige taken zijn, kun je met
mapbeknoptere code schrijven. - Als nauwkeurige controle over de uitvoeringsvolgorde of foutafhandeling belangrijk is, combineer dan
submitmetas_completed.
Voordelen van het gebruik van concurrent
Met behulp van de concurrent module kun je asynchrone verwerking veilig en intuïtief uitvoeren.
De belangrijkste voordelen zijn als volgt:.
- Je hoeft je niet bezig te houden met low-level beheer van threads of processen.
- Het wordt geleverd als onderdeel van de standaardbibliotheek van Python, dus je kunt het met vertrouwen gebruiken.
- De code wordt beter leesbaar en onderhoudbaar.
- Het is ideaal als eerste stap om te leren over gelijktijdigheid en parallelisme.
Door deze richtlijnen te volgen, kun je fouten in implementaties met concurrent sterk verminderen.
Samenvatting
De concurrent module is de standaardoptie voor praktische gelijktijdigheid en parallelisme in Python. Hiermee kun je de prestaties verbeteren zonder de inhoud van je verwerking ingrijpend aan te passen, wat in de praktijk een groot voordeel is. Met concurrent kun je asynchrone verwerking beknopt implementeren, terwijl je de foutafhandeling en uitvoeringscontrole veilig beheert.
Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.