Modulet `concurrent` i Python
I denne artikel vil vi forklare concurrent-modulet i Python.
Mens vi tydeliggør begreberne samtidighed og parallelisme, vil vi forklare, hvordan man implementerer asynkron behandling ved hjælp af concurrent-modulet med praktiske eksempler.
YouTube Video
Modulet concurrent i Python
Når man vil gøre behandling hurtigere i Python, er det vigtigt at være opmærksom på forskellene mellem samtidighed og parallelisme. concurrent-modulet er et vigtigt middel til sikkert og enkelt at håndtere asynkron behandling med disse forskelle i tankerne.
Forskellen mellem samtidighed og parallelisme
-
Samtidighed betyder at designe en proces, så flere opgaver kører ved at skifte mellem dem i små arbejdsenheder. Selv hvis opgaverne ikke faktisk kører samtidig, kan brug af "ventetid" gøre hele processen mere effektiv.
-
Parallelisme er en mekanisme, der fysisk udfører flere opgaver på samme tid. Ved at bruge flere CPU-kerner udføres behandlingen samtidigt.
Begge er teknikker til at gøre behandlingen hurtigere, men samtidighed handler om designspørgsmålet 'hvordan man går videre', mens parallelisme handler om eksekveringsspørgsmålet 'hvordan det kører', og derfor adskiller de sig grundlæggende fra hinanden.
Hvad er concurrent-modulet?
concurrent er et standardbibliotek i Python, der tilbyder et højniveau-API til at håndtere samtidighed og parallelisme sikkert og enkelt. Det er designet, så du kan fokusere på 'at udføre opgaver' uden at skulle bekymre dig om lavniveauoperationer som at oprette og administrere tråde eller processer.
Roller for ThreadPoolExecutor og ProcessPoolExecutor
concurrent-modulet tilbyder to hovedmuligheder afhængigt af opgavens karakter.
-
ThreadPoolExecutorDette er velegnet til samtidige implementeringer, især opgaver med mange I/O-ventetider, såsom netværks- eller filoperationer. Ved at skifte mellem opgaver udnyttes ventetiden effektivt. -
ProcessPoolExecutorDenne implementering sigter mod parallel behandling og er velegnet til CPU-intensive opgaver. Den bruger flere processer til at udnytte tilgængelige CPU-kerner parallelt.
Dermed er en hovedfunktion ved concurrent-modulet, at det giver en struktur, hvor du korrekt kan vælge mellem samtidighed og parallelisme efter behov.
Grundlæggende om ThreadPoolExecutor (Til I/O-opgaver)
ThreadPoolExecutor er velegnet til I/O-baserede opgaver, såsom netværkskommunikation og filoperationer. Den fordeler opgaver på flere tråde og udnytter ventetiden effektivt.
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())- I dette eksempel udføres flere I/O-opgaver, der venter i ét sekund, samtidigt. Ved at bruge
submitregistreres funktionkald som asynkrone opgaver, og ved at kalderesult()kan du vente på afslutningen og få resultaterne, hvilket gør det muligt at implementere samtidig behandling, der udnytter ventetiden effektivt og kompakt.
Simpel samtidighed med map
Hvis kompleks styring ikke er nødvendig, kan det gøre koden mere enkel at bruge map.
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)- I dette eksempel udføres flere I/O-opgaver samtidigt ved hjælp af
ThreadPoolExecutor.map. Damapreturnerer resultater i samme rækkefølge som inputtene, kan du skrive kode tæt på sekventiel behandling, og samtidig eksekvering er mulig uden at tænke på asynkron behandling – dette er en stor fordel.
Grundlæggende om ProcessPoolExecutor (Til CPU-tunge opgaver)
Ved tunge beregninger, der udnytter CPU'en fuldt ud, bør du bruge processer frem for tråde. Dette gør det muligt at undgå begrænsningerne ved Global Interpreter Lock (GIL).
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)I dette eksempel udføres CPU-intensive beregninger parallelt ved hjælp af ProcessPoolExecutor. Da det indebærer oprettelse af processer, kræves en __main__-beskyttelse, hvilket muliggør sikker parallel behandling med flere CPU-kerner.
Behandling i afslutningsrækkefølge med as_completed
as_completed er nyttigt, når du vil håndtere resultater i den rækkefølge, de afsluttes.
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())- I dette eksempel udføres flere asynkrone opgaver samtidig, og resultaterne hentes i den rækkefølge, de afsluttes. Ved at bruge
as_completedkan du hurtigt håndtere resultater uanset opgavernes rækkefølge, hvilket gør det velegnet til statusvisning eller situationer, der kræver sekventiel behandling.
Håndtering af undtagelser
I concurrent udløses undtagelser, når du kalder result().
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)- Dette eksempel demonstrerer, at selvom nogle opgaver udløser undtagelser, fortsætter de andre, og du kan håndtere undtagelser individuelt, når du henter resultaterne. Ved at bruge
concurrent'sFutureer det vigtigt, at både succeser og fejl i asynkron behandling kan håndteres sikkert.
Retningslinjer for valg mellem tråde og processer
For at bruge samtidighed og parallelisme effektivt, er det vigtigt at vælge den rette tilgang baseret på opgavens karakter.
I praksis kan følgende kriterier hjælpe dig med at træffe en beslutning.
- For processer med mange I/O-ventetider, såsom kommunikation eller filoperationer, skal du bruge
ThreadPoolExecutor. - For CPU-tunge beregningsopgaver, brug
ProcessPoolExecutor. - Hvis der er mange enkle opgaver, kan brug af
mapgøre din kode mere enkel. - Hvis præcis kontrol med eksekveringsrækkefølge eller undtagelseshåndtering er vigtig, kombiner da
submitmedas_completed.
Fordele ved at bruge concurrent
Ved at bruge concurrent-modulet kan du håndtere asynkron behandling sikkert og intuitivt.
De vigtigste fordele er følgende:.
- Du behøver ikke bekymre dig om lavniveau håndtering af tråde eller processer.
- Det leveres som en del af Pythons standardbibliotek, så du kan bruge det med tryghed.
- Koden bliver mere læsbar og lettere at vedligeholde.
- Det er ideelt som første skridt til at lære om samtidighed og parallelisme.
At have disse retningslinjer i baghovedet kan markant reducere fejl i implementeringer, der bruger concurrent.
Sammendrag
concurrent-modulet er standardvalget for praktisk samtidighed og parallelisme i Python. Det gør det muligt at øge ydeevnen uden at ændre meget i din behandlingsstruktur, hvilket er en betydelig fordel i praksis. Ved at bruge concurrent kan du implementere asynkron behandling kompakt og samtidig håndtere undtagelser og eksekveringskontrol sikkert.
Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.