`threading`-modulen i Python

`threading`-modulen i Python

Denne artikkelen forklarer threading-modulen i Python.

YouTube Video

threading-modulen i Python

threading-modulen i Python er et standardbibliotek som støtter multitrådprogrammering. Ved å bruke tråder kan flere prosesser kjøre samtidig, noe som kan forbedre ytelsen til programmer, spesielt i situasjoner som involverer blokkerende operasjoner som I/O-venting. På grunn av Pythons Global Interpreter Lock (GIL) er effektiviteten til multitråding begrenset for CPU-krevende operasjoner, men det fungerer effektivt for I/O-krevende operasjoner.

De følgende seksjonene forklarer det grunnleggende om bruk av threading-modulen og hvordan man kontrollerer tråder.

Grunnleggende bruk av tråder

Opprette og kjøre tråder

For å opprette en tråd og utføre samtidige prosesser, bruk klassen threading.Thread. Angi målfunksjonen for å opprette en tråd og kjøre den tråden.

 1import threading
 2import time
 3
 4# Function to be executed in a thread
 5def worker():
 6    print("Worker thread started")
 7    time.sleep(2)
 8    print("Worker thread finished")
 9
10# Create and start the thread
11thread = threading.Thread(target=worker)
12thread.start()
13
14# Processing in the main thread
15print("Main thread continues to run")
16
17# Wait for the thread to finish
18thread.join()
19print("Main thread finished")
  • I dette eksemplet kjører worker-funksjonen i en separat tråd, mens hovedtråden fortsetter å operere. Ved å kalle metoden join(), venter hovedtråden til undertråden er ferdig.

Navngivning av tråder

Å gi tråder meningsfulle navn gjør logging og feilsøking enklere. Du kan angi det med name-argumentet.

 1import threading
 2import time
 3
 4# Function to be executed in a thread
 5def worker():
 6    print("Worker thread started")
 7    time.sleep(2)
 8    print("Worker thread finished")
 9
10t = threading.Thread(
11    target=worker,
12    args=("named-worker", 0.3),
13    name="MyWorkerThread"
14)
15
16t.start()
17
18print("Active threads:", threading.active_count())
19for th in threading.enumerate():
20    print(" -", th.name)
21
22t.join()
  • threading.enumerate() returnerer en liste over aktive tråder, noe som er nyttig for feilsøking og overvåkning av tilstand.

Arve trådklassen

Hvis du vil tilpasse trådklassen, kan du definere en ny klasse ved å arve klassen threading.Thread.

 1import threading
 2import time
 3
 4# Inherit from the Thread class
 5class WorkerThread(threading.Thread):
 6    def __init__(self, name, delay, repeat=3):
 7        super().__init__(name=name)
 8        self.delay = delay
 9        self.repeat = repeat
10        self.results = []
11
12    def run(self):
13        for i in range(self.repeat):
14            msg = f"{self.name} step {i+1}"
15            print(msg)
16            self.results.append(msg)
17            time.sleep(self.delay)
18
19# Create and start the threads
20t1 = WorkerThread("Worker-A", delay=0.4, repeat=3)
21t2 = WorkerThread("Worker-B", delay=0.2, repeat=5)
22
23t1.start()
24t2.start()
25
26t1.join()
27t2.join()
28
29print("Results A:", t1.results)
30print("Results B:", t2.results)
  • I dette eksempelet overstyres run()-metoden for å definere trådens oppførsel, slik at hver tråd kan ha egne data. Dette er nyttig når tråder utfører kompleks behandling, eller når du vil at hver tråd skal ha egne, uavhengige data.

Synkronisering mellom tråder

Når flere tråder får tilgang til delte ressurser samtidig, kan det oppstå datakappløp. For å forhindre dette tilbyr threading-modulen flere synkroniseringsmekanismer.

Lås (Lock)

Lock-objektet brukes til å implementere eksklusiv kontroll over ressurser mellom tråder. Mens én tråd låser en ressurs, kan ikke andre tråder få tilgang til den ressursen.

 1import threading
 2
 3lock = threading.Lock()
 4shared_resource = 0
 5
 6def worker():
 7    global shared_resource
 8    with lock:  # Acquire the lock
 9        local_copy = shared_resource
10        local_copy += 1
11        shared_resource = local_copy
12
13threads = [threading.Thread(target=worker) for _ in range(5)]
14
15for t in threads:
16    t.start()
17
18for t in threads:
19    t.join()
20
21print(f"Final value of shared resource: {shared_resource}")
  • I dette eksemplet får fem tråder tilgang til en delt ressurs, men Lock brukes for å forhindre at flere tråder endrer dataene samtidig.

Re-entrerende lås (RLock)

Hvis samme tråd må ta en lås flere ganger, bruk en RLock (re-entrerende lås). Dette er nyttig ved rekursive kall eller bibliotekkall som kan ta lås gjennom flere kall.

 1import threading
 2
 3rlock = threading.RLock()
 4shared = []
 5
 6def outer():
 7    with rlock:
 8        shared.append("outer")
 9        inner()
10
11def inner():
12    with rlock:
13        shared.append("inner")
14
15t = threading.Thread(target=outer)
16t.start()
17t.join()
18print(shared)
  • Med RLock kan samme tråd ta en lås den allerede holder, noe som bidrar til å forhindre fastlåsning ved nøstede låser.

Betingelse (Condition)

Condition brukes for at tråder skal vente til en spesifikk betingelse er oppfylt. Når en tråd oppfyller en betingelse, kan du kalle notify() for å varsle en annen tråd, eller notify_all() for å varsle alle ventende tråder.

Nedenfor er et eksempel på en produsent og konsument som bruker en Condition.

 1import threading
 2
 3condition = threading.Condition()
 4shared_data = []
 5
 6def producer():
 7    with condition:
 8        shared_data.append(1)
 9        print("Produced an item")
10        condition.notify()  # Notify the consumer
11
12def consumer():
13    with condition:
14        condition.wait()  # Wait until the condition is met
15
16        item = shared_data.pop(0)
17        print(f"Consumed an item: {item}")
18
19# Create the threads
20producer_thread = threading.Thread(target=producer)
21consumer_thread = threading.Thread(target=consumer)
22
23consumer_thread.start()
24producer_thread.start()
25
26producer_thread.join()
27consumer_thread.join()
  • Denne koden bruker en Condition slik at produsenten varsler når data legges til, og konsumenten venter på varslet før den henter dataene, og oppnår dermed synkronisering.

Tråd-demonisering

Daemontråder avsluttes tvangsmessig når hovedtråden avsluttes. Vanlige tråder må vente for å sluttere, mens daemontråder avsluttes automatisk.

 1import threading
 2import time
 3
 4def worker():
 5    while True:
 6        print("Working...")
 7        time.sleep(1)
 8
 9# Create a daemon thread
10thread = threading.Thread(target=worker)
11thread.daemon = True  # Set as a daemon thread
12
13thread.start()
14
15# Processing in the main thread
16time.sleep(3)
17print("Main thread finished")
  • I dette eksemplet er worker-tråden demonisert, slik at den avsluttes tvangsmessig når hovedtråden slutter.

Trådhåndtering med ThreadPoolExecutor

Bortsett fra threading-modulen, kan du bruke ThreadPoolExecutor fra concurrent.futures-modulen for å administrere en trådpool og utføre oppgaver parallelt.

 1from concurrent.futures import ThreadPoolExecutor
 2import time
 3
 4def worker(seconds):
 5    print(f"Sleeping for {seconds} second(s)")
 6    time.sleep(seconds)
 7    return f"Finished sleeping for {seconds} second(s)"
 8
 9with ThreadPoolExecutor(max_workers=3) as executor:
10    futures = [executor.submit(worker, i) for i in range(1, 4)]
11    for future in futures:
12        print(future.result())
  • ThreadPoolExecutor oppretter en trådpool og behandler oppgaver effektivt. Angi antall tråder som skal kjøres samtidig med max_workers.

Hendelseskommunikasjon mellom tråder

Ved hjelp av threading.Event kan du sette flagg mellom tråder for å varsle andre tråder om at en hendelse har skjedd.

 1import threading
 2import time
 3
 4event = threading.Event()
 5
 6def worker():
 7    print("Waiting for event to be set")
 8    event.wait()  # Wait until the event is set
 9
10    print("Event received, continuing work")
11
12thread = threading.Thread(target=worker)
13thread.start()
14
15time.sleep(2)
16print("Setting the event")
17event.set()  # Set the event and notify the thread
  • Denne koden demonstrerer en mekanisme hvor arbeidstråden venter på Event-signalet og fortsetter når hovedtråden kaller event.set().

Unntakshåndtering og avslutning av tråder

Når unntak oppstår i tråder, blir de ikke direkte sendt til hovedtråden, så det kreves et mønster for å fange og dele unntak.

 1import threading
 2import queue
 3
 4def worker(err_q):
 5    try:
 6        raise ValueError("Something bad")
 7    except Exception as e:
 8        err_q.put(e)
 9
10q = queue.Queue()
11t = threading.Thread(target=worker, args=(q,))
12t.start()
13t.join()
14if not q.empty():
15    exc = q.get()
16    print("Worker raised:", exc)
  • Ved å legge unntak i en Queue og hente dem i hovedtråden, kan du pålitelig oppdage feil. Hvis du bruker concurrent.futures.ThreadPoolExecutor, blir unntak kastet på nytt med future.result(), noe som gjør dem enklere å håndtere.

GIL (Global Interpreter Lock) og dens effekter

På grunn av mekanismen med GIL (Global Interpreter Lock) i CPython, kjøres ikke flere Python-bytecodes faktisk samtidig i samme prosess. For oppgaver som er CPU-intensive, som tunge beregninger, anbefales det å bruke multiprocessing. På den annen side, for I/O-baserte oppgaver som filinnlesning eller nettverkskommunikasjon, fungerer threading effektivt.

Sammendrag

Ved å bruke Pythons threading-modul kan du implementere multitrådede programmer og utføre flere prosesser samtidig. Med synkroniseringsmekanismer som Lock og Condition kan du trygt få tilgang til delte ressurser og utføre kompleks synkronisering. I tillegg gjør bruk av daemontråder eller ThreadPoolExecutor trådhåndtering og effektiv parallellbehandling enklere.

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