`threading`-modulen i Python
Den här artikeln förklarar threading-modulen i Python.
YouTube Video
threading-modulen i Python
threading-modulen i Python är ett standardbibliotek som stöder flertrådsprogrammering. Genom att använda trådar kan flera processer köras samtidigt, vilket kan förbättra prestandan hos program, särskilt vid blockerande operationer som I/O-väntetider. På grund av Pythons Global Interpreter Lock (GIL) är effektiviteten hos multitrådning begränsad för CPU-intensiva operationer, men det fungerar effektivt för I/O-intensiva operationer.
Följande avsnitt förklarar grunderna i att använda threading-modulen och hur man kontrollerar trådar.
Grundläggande användning av trådar
Skapa och köra trådar
För att skapa en tråd och utföra parallell bearbetning, använd klassen threading.Thread. Ange målfunktionen för att skapa en tråd och köra 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 detta exempel körs funktionen
workeri en separat tråd, medan huvudtråden fortsätter att fungera. Genom att anropa metodenjoin()väntar huvudtråden på att undertråden ska slutföras.
Namngivning av trådar
Att ge trådar meningsfulla namn gör loggning och felsökning enklare. Du kan ange det med argumentet name.
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()returnerar en lista över aktuella trådar, vilket är användbart för felsökning och övervakning av tillstånd.
Ärva trådklassen
Om du vill anpassa den tråd-körande klassen kan du definiera en ny klass genom att ärva 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 det här exemplet åsidosätts metoden
run()för att definiera trådens beteende, vilket gör att varje tråd kan behålla sin egen data. Detta är användbart när trådar utför komplex bearbetning eller när du vill att varje tråd ska ha sin egen oberoende data.
Synkronisering mellan trådar
När flera trådar får åtkomst till delade resurser samtidigt kan datakörning inträffa. För att förhindra detta tillhandahåller threading-modulen flera synkroniseringsmekanismer.
Lås (Lock)
Lock-objektet används för att implementera exklusiv kontroll av resurser mellan trådar. Medan en tråd låser en resurs kan andra trådar inte få åtkomst till den resursen.
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 detta exempel får fem trådar åtkomst till en delad resurs, men
Lockanvänds för att förhindra att flera trådar ändrar data samtidigt.
Återinträdbart lås (RLock)
Om samma tråd behöver ta ett lås flera gånger, använd ett RLock (återinträdbart lås). Detta är användbart för rekursiva anrop eller för bibliotek som kan ta lås över olika anrop.
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
RLockkan samma tråd åter ta ett lås den redan håller, vilket hjälper till att förhindra dödlägen vid nästlade låsanrop.
Villkor (Condition)
Condition används för att låta trådar vänta tills ett specifikt villkor är uppfyllt. När en tråd uppfyller ett villkor kan du anropa notify() för att meddela en annan tråd, eller notify_all() för att meddela alla väntande trådar.
Nedan är ett exempel med en producent och konsument som använder ett Condition-objekt.
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()- Denna kod använder ett
Conditionså att producenten meddelar när data har lagts till, och konsumenten väntar på detta meddelande innan den hämtar data, vilket uppnår synkronisering.
Daemonisering av trådar
Daemontrådar avslutas tvångsmässigt när huvudtråden slutar. Medan vanliga trådar måste vänta på att avslutas, avslutas daemontrådar automatiskt.
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 det här exemplet är
worker-tråden daemoniserad, så den avslutas tvångsmässigt när huvudtråden slutar.
Hantering av trådar med ThreadPoolExecutor
Förutom threading-modulen kan du använda ThreadPoolExecutor från concurrent.futures-modulen för att hantera en pool av trådar och köra uppgifter parallellt.
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())ThreadPoolExecutorskapar en trådpool och hanterar uppgifter effektivt. Ange antalet trådar som ska köras samtidigt medmax_workers.
Händelsekommunikation mellan trådar
Genom att använda threading.Event kan du sätta flaggor mellan trådar för att meddela andra trådar om att en händelse inträffat.
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- Denna kod visar en mekanism där arbetstråden väntar på
Event-signalen och återupptar bearbetningen när huvudtråden anroparevent.set().
Undantagshantering och trådterminering i trådar
När undantag uppstår i trådar vidarebefordras de inte direkt till huvudtråden, så ett mönster behövs för att fånga och dela undantag.
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)- Genom att lägga undantag i en
Queueoch hämta dem i huvudtråden kan du pålitligt upptäcka fel. Om du använderconcurrent.futures.ThreadPoolExecutorkastas undantag igen medfuture.result(), vilket gör dem enklare att hantera.
GIL (Global Interpreter Lock) och dess effekter
På grund av mekanismen med GIL (Global Interpreter Lock) i CPython körs inte flera Python-bytekoder faktiskt samtidigt inom samma process. För uppgifter som är CPU-intensiva, till exempel tunga beräkningar, rekommenderas det att använda multiprocessing. Å andra sidan fungerar threading effektivt för I/O-bundna uppgifter såsom filläsning eller nätverkskommunikation.
Sammanfattning
Med Pythons threading-modul kan du implementera flertrådade program och köra flera processer samtidigt. Med synkroniseringsmekanismer som Lock och Condition kan du säkert komma åt delade resurser och utföra komplex synkronisering. Dessutom blir trådhantering och effektiv parallell bearbetning enklare med daemontrådar eller ThreadPoolExecutor.
Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.