Multithreading i Python: En komplett guide for nybegynnere

Utforsk Multithreading i Python med «threading»-modulen

I denne veiledningen vil du oppdage hvordan du benytter Pythons integrerte «threading»-modul for å dykke ned i multithreading-funksjonaliteten.

Vi starter med å se på de grunnleggende forskjellene mellom prosesser og tråder, før vi utforsker hvordan multithreading fungerer i Python, samtidig som vi forklarer konseptene samtidighet og parallellitet. Du vil så lære hvordan du starter og administrerer én eller flere tråder i Python ved hjelp av den medfølgende «threading»-modulen.

La oss begynne med det grunnleggende.

Forskjellen mellom prosesser og tråder

Hva er en prosess?

En prosess refererer til enhver instans av et program som er i gang.

Dette kan være alt fra et Python-skript til en nettleser som Chrome, eller en videokonferanseapplikasjon. Du kan se aktive prosesser og tråder på datamaskinen din ved å åpne Oppgavebehandling og gå til Ytelse -> CPU.

Forstå prosesser og tråder

En prosess har sitt eget dedikerte minne der den lagrer koden og dataene den trenger for å fungere.

En prosess kan bestå av en eller flere tråder. En tråd er den minste sekvensen av instruksjoner som operativsystemet kan utføre, og den representerer flyten av utførelse.

Hver tråd har sin egen stakk og registre, men ikke dedikert minne. Alle trådene i en prosess kan få tilgang til prosessens data, da minne og data deles mellom trådene.

En CPU med N kjerner kan parallelt utføre N prosesser samtidig. Tråder innenfor samme prosess kan derimot ikke kjøre parallelt, men de kan kjøre samtidig. Vi vil se nærmere på forskjellen mellom samtidighet og parallellisme senere.

La oss oppsummere forskjellen mellom prosesser og tråder basert på det vi har lært:

Funksjon Prosess Tråd
Minne Dedikert minne Delt minne
Utførelsesmodus Parallell, samtidig Samtidig, men ikke parallell
Håndtering av utførelse Operativsystem CPython Interpreter

Multithreading i Python

I Python sikrer Global Interpreter Lock (GIL) at kun én tråd kan hente låsen og kjøre av gangen. Alle tråder må ha denne låsen for å kunne utføres. Dette sørger for at kun én enkelt tråd kjører om gangen, og forhindrer ekte multithreading.

Tenk deg for eksempel to tråder, t1 og t2, i samme prosess. Fordi tråder deler data, kan t2 endre en verdi k som t1 leser. Dette kan føre til vranglås og uforutsigbare resultater. Men kun én av trådene kan få låsen og kjøre til enhver tid, noe som gjør GIL essensiell for trådsikkerhet.

Så, hvordan oppnår vi multithreading i Python? La oss utforske konseptene samtidighet og parallellisme.

Samtidighet vs. Parallellisme

Se for deg en CPU med flere kjerner. I illustrasjonen nedenfor har CPU-en fire kjerner, noe som betyr at vi kan ha fire operasjoner som kjører parallelt samtidig.

Hvis vi har fire prosesser, kan hver av dem kjøre uavhengig og samtidig på hver av kjernene. Anta at hver prosess har to tråder.

For å forstå tråding bedre, la oss gå fra flerkjerne- til enkeltkjerne-arkitektur. Som nevnt, kan bare én tråd være aktiv om gangen, men prosessorkjernen kan bytte raskt mellom trådene.

Tråder som venter på I/O-operasjoner, som brukerinndata, database- eller filoperasjoner, kan frigjøre låsen mens de venter. I løpet av denne ventetiden kan en annen tråd få kjøre. Ventetiden kan også være en enkel «sleep»-operasjon.

Oppsummert: Ved venteprosesser frigjør tråden låsen, slik at prosessorkjernen kan bytte til en annen tråd. Den første tråden fortsetter når ventetiden er over. Denne prosessen, der prosessorkjernen veksler mellom tråder, legger til rette for multithreading. ✅

Hvis du trenger ekte parallellitet på prosessnivå, bør du vurdere å bruke multiprosessering.

Første steg med Pythons trådmodul

Python kommer med en trådmodul som du kan importere til Python-skriptet ditt:

import threading

For å opprette et trådobjekt, bruk trådkonstruktøren: threading.Thread(...). Dette er den vanlige syntaksen for de fleste trådimplementeringer:

threading.Thread(target=..., args=...)

Hvor:

  • target er argumentet som spesifiserer en Python-funksjon som skal kjøres.
  • args er en tuppel med argumenter funksjonen krever.

Du trenger Python 3.x for å kjøre kodeeksemplene. Last ned koden og følg med.

Hvordan definere og kjøre tråder

La oss definere en tråd som kjører en funksjon.

Målfunksjonen er some_func:


import threading
import time

def some_func():
    print("Kjører some_func...")
    time.sleep(2)
    print("Ferdig med å kjøre some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

La oss se hva koden gjør:

  • Importerer «threading» og «time»-modulene.
  • some_func-funksjonen har print-setninger og en pause på to sekunder via time.sleep(n).
  • Vi definerer en tråd thread1 som kjører some_func. threading.Thread(target=...) oppretter et trådobjekt.
  • Viktig: Angi funksjonsnavnet, ikke et funksjonskall; bruk some_func, ikke some_func().
  • Trådobjektet starter ikke tråden; start()-metoden gjør det.
  • Vi bruker active_count() for å se antall aktive tråder.

Python-skriptet kjører i hovedtråden, og vi lager en ny tråd (thread1) for some_func, så det er to aktive tråder:


# Output
Kjører some_func...
2
Ferdig med å kjøre some_func.

Som du ser, starter tråd1 og skriver den første setningen. Men under pausen, bytter prosessoren til hovedtråden og skriver ut antall aktive tråder – uten å vente på at tråd1 er ferdig.

Vent på at tråder skal fullføres

For å la tråd1 fullføre før resten av koden fortsetter, kan du bruke join()-metoden etter at du har startet tråden. Dette vil vente på at tråd1 fullfører.


import threading
import time

def some_func():
    print("Kjører some_func...")
    time.sleep(2)
    print("Ferdig med å kjøre some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

Nå fullfører tråd1 før vi skriver ut antall aktive tråder, så kun hovedtråden kjører, og antall aktive tråder er 1. ✅


# Output
Kjører some_func...
Ferdig med å kjøre some_func.
1

Kjøre flere tråder i Python

La oss nå opprette to tråder for å kjøre to forskjellige funksjoner.

count_down-funksjonen tar et tall som argument og teller ned fra det tallet til null:


def count_down(n):
    for i in range(n,-1,-1):
        print(i)

Vi definerer også count_up, som teller opp fra null til et gitt tall:


def count_up(n):
    for i in range(n+1):
        print(i)

📑 Når du bruker range() med syntaksen range(start, stop, step), inkluderes ikke stopp-verdien som standard.

– For å telle ned fra et tall til null, bruk negativ trinnverdi (-1) og stopp-verdi på -1 slik at null inkluderes.

– For å telle opp til n, må du sette stoppverdien til n+1. Siden standardverdiene for start og trinn er henholdsvis 0 og 1, kan du bruke range(n+1) for å få tallene 0 til n.

La oss opprette to tråder, thread1 og thread2, for å kjøre henholdsvis count_down og count_up. Vi legger også til print-setninger og pauser for begge funksjonene.

Når du oppretter trådobjektene, vær oppmerksom på at argumentene til målfunksjonen må spesifiseres som en tuppel i args-parameteren. Siden begge funksjonene tar ett argument, må du sette inn et komma etter verdien. Dette sørger for at argumentet sendes som en tuppel.


import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Kjører tråd1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Kjører tråd2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

I output:

  • count_up kjører i thread2 og teller opp fra 0 til 5.
  • count_down kjører i thread1 og teller ned fra 10 til 0.

# Output
Kjører tråd1....
10
Kjører tråd2...
0
Kjører tråd1....
9
Kjører tråd2...
1
Kjører tråd1....
8
Kjører tråd2...
2
Kjører tråd1....
7
Kjører tråd2...
3
Kjører tråd1....
6
Kjører tråd2...
4
Kjører tråd1....
5
Kjører tråd2...
5
Kjører tråd1....
4
Kjører tråd1....
3
Kjører tråd1....
2
Kjører tråd1....
1
Kjører tråd1....
0

Du ser at thread1 og thread2 kjører vekselvis, ettersom begge har en venteoperasjon (sleep). Når count_up er ferdig med å telle til 5, er thread2 ikke aktiv lenger. Derfor får vi output fra kun thread1 etter det.

Oppsummering

I denne veiledningen lærte du hvordan du bruker Pythons «threading»-modul for å implementere multithreading. Her er en oppsummering av de viktigste punktene:

  • Trådkonstruktøren lager et trådobjekt. threading.Thread(target=funksjon,args=(argumenter)) oppretter en tråd som kjører funksjon med argumenter.
  • Python-programmet kjører i hovedtråden. Trådobjektene du lager, er ekstra tråder. active_count() gir antall aktive tråder.
  • Start en tråd med start()-metoden, og vent til den fullfører med join().

Du kan eksperimentere med flere eksempler ved å endre ventetider, prøve andre I/O-operasjoner, og mer. Inkluder multithreading i dine kommende Python-prosjekter. Lykke til med kodingen! 🎉