Modulen `concurrent` i Python

Modulen `concurrent` i Python

I denne artikkelen vil vi forklare concurrent-modulen i Python.

Samtidig som vi klargjør begrepene samtidighet og parallellisme, vil vi forklare hvordan man implementerer asynkron behandling ved å bruke concurrent-modulen med praktiske eksempler.

YouTube Video

Modulen concurrent i Python

Når du skal øke behandlingshastigheten i Python, er det viktig å være oppmerksom på forskjellene mellom samtidighet og parallellisme. concurrent-modulen er et viktig verktøy for å håndtere asynkron behandling sikkert og enkelt med disse forskjellene i tankene.

Forskjellen mellom samtidighet og parallellisme

  • Samtidighet betyr å designe en prosess slik at flere oppgaver går fremover ved å bytte mellom dem i små arbeidsenheter. Selv om oppgavene ikke faktisk kjører samtidig, kan man gjøre hele prosessen mer effektiv ved å utnytte "ventetid".

  • Parallellisme er en mekanisme der flere oppgaver faktisk kjøres samtidig. Ved å bruke flere CPU-kjerner, behandles oppgaver parallelt.

Begge er teknikker for å øke hastigheten på behandlingen, men samtidighet handler om hvordan prosessen utformes, mens parallellisme handler om hvordan den kjøres, noe som gjør dem grunnleggende forskjellige.

Hva er concurrent-modulen?

concurrent er et standardbibliotek i Python som gir et høynivå API for å håndtere samtidighet og parallellisme sikkert og enkelt. Den er designet slik at du kan fokusere på å 'utføre oppgaver' uten å måtte bekymre deg for lavnivå-operasjoner som å lage og håndtere tråder eller prosesser.

Rollene til ThreadPoolExecutor og ProcessPoolExecutor

concurrent-modulen gir to hovedvalg, avhengig av type oppgave.

  • ThreadPoolExecutor Dette passer for samtidige implementasjoner, særlig oppgaver med mye I/O-venting, for eksempel nettverks- eller filoperasjoner. Ved å bytte mellom oppgaver utnyttes ventetid effektivt.

  • ProcessPoolExecutor Denne implementeringen retter seg mot parallell behandling og passer for CPU-intensive oppgaver. Den bruker flere prosesser for å utnytte alle tilgjengelige CPU-kjerner parallelt.

Dermed er en hovedfunksjon i concurrent-modulen at den gir en struktur som lar deg velge riktig mellom samtidighet og parallellisme etter behov.

Grunnleggende om ThreadPoolExecutor (for I/O-oppgaver)

ThreadPoolExecutor passer for I/O-baserte oppgaver, som nettverkskommunikasjon og filoperasjoner. Den fordeler oppgaver mellom flere tråder og utnytter 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 eksempelet utføres flere I/O-oppgaver som venter i ett sekund samtidig. Ved å bruke submit blir funksjonskall registrert som asynkrone oppgaver, og ved å kalle result() kan du vente på ferdigstillelse og hente resultatene, noe som gjør det mulig å implementere samtidighetsbehandling som utnytter ventetid på en enkel måte.

Enkel samtidighet med map

Hvis kompleks kontroll ikke er nødvendig, kan bruk av map gjøre koden mer oversiktlig.

 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 eksempelet kjøres flere I/O-oppgaver samtidig med ThreadPoolExecutor.map. Siden map returnerer resultatene i samme rekkefølge som input, kan du skrive kode som ligner sekvensiell behandling, og samtidig utførelse er mulig uten å måtte tenke på asynkron behandling – dette er en stor fordel.

Grunnleggende om ProcessPoolExecutor (for CPU-baserte oppgaver)

For tunge beregninger som utnytter CPUen maks, bør man bruke prosesser i stedet for tråder. Dette lar deg unngå begrensningene med 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 eksempelet blir CPU-intensive oppgaver kjørt parallelt med ProcessPoolExecutor. Siden prosesser opprettes, kreves det en __main__-beskyttelse, noe som gir sikker parallell behandling ved bruk av flere CPU-kjerner.

Behandling etter ferdigstillelsesrekkefølge med as_completed

as_completed er nyttig når du vil håndtere resultater i den rekkefølgen de fullføres.

 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 eksempelet kjøres flere asynkrone oppgaver samtidig, og resultatene hentes i den rekkefølgen de fullføres. Ved å bruke as_completed kan du håndtere resultater raskt uavhengig av oppgavenes rekkefølge, noe som passer til fremvisning av fremdrift eller situasjoner der sekvensiell behandling trengs.

Håndtering av unntak

I concurrent kastes unntak når du kaller 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 eksempelet viser at selv om noen oppgaver kaster unntak, fortsetter de andre, og du kan behandle unntak individuelt når du henter resultatene. Ved å bruke concurrent sin Future, er det viktig at suksess og feil i asynkron behandling kan håndteres trygt.

Retningslinjer for valg mellom tråder og prosesser

For å bruke samtidighet og parallellisme effektivt, er det viktig å velge riktig tilnærming basert på oppgavens natur.

I praksis kan følgende kriterier hjelpe deg med å velge.

  • For prosesser med mye I/O-venting, som kommunikasjon eller filoperasjoner, bruk ThreadPoolExecutor.
  • For CPU-baserte, tunge beregninger bruk ProcessPoolExecutor.
  • Hvis det er mange enkle oppgaver, kan bruk av map gjøre koden mer konsis.
  • Hvis nøyaktig kontroll over utførelsesrekkefølge eller unntakshåndtering er viktig, kombiner submit med as_completed.

Fordeler med å bruke concurrent

Ved å bruke concurrent-modulen kan du håndtere asynkron prosessering trygt og intuitivt.

Hovedfordelene er:.

  • Du slipper å styre med lavnivå håndtering av tråder eller prosesser.
  • Den er en del av Pythons standardbibliotek, så du kan bruke den med trygghet.
  • Koden blir mer lesbar og vedlikeholdbar.
  • Den er ideell som et første skritt for å lære om samtidighet og parallellisme.

Ved å følge disse retningslinjene kan du redusere feil betraktelig i implementasjoner med concurrent.

Sammendrag

concurrent-modulen er standardvalget for praktisk samtidighet og parallellisme i Python. Den lar deg øke ytelsen uten å endre mye på innholdet i prosessen, noe som er en stor fordel i praksis. Ved å bruke concurrent kan du implementere asynkron behandling konsist, samtidig som du trygt håndterer unntak og utførelseskontroll.

Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.

YouTube Video