15 Multithreading und -processing
Bei I/O-bound Tasks helfen Threads oder Asyncio besonders gut, weil sie während der Wartezeit andere Aufgaben erledigen können.
Bei CPU-bound Tasks ist der GIL ein Problem in Threads – daher ist Multiprocessing hier besser.
Beispiel mit multiprocessing.Queue:
from multiprocessing import Process, Queue
def worker(q):
q.put("Hallo aus dem Prozess")
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
print(q.get()) # Ausgabe: Hallo aus dem Prozess
p.join()
await wird eine Task "geparkt", und der Loop schaut, ob eine andere weiterlaufen kann.Beispiel mit einem Loop:
import asyncio
async def task():
print("Task läuft")
await asyncio.sleep(1)
print("Task beendet")
loop = asyncio.get_event_loop()
loop.run_until_complete(task())
+--------------+
| Main Process |
+--------------+
|
+--------------+-------------+
| | |
v v v
+----------+ +----------+ +----------+
| Thread 1 | | Thread 2 | | Thread 3 |
+----------+ +----------+ +----------+
(teilen sich Speicher & Ressourcen)
Beispiel:
import threading
import time
def worker():
print("Thread startet")
time.sleep(2)
print("Thread endet")
t1 = threading.Thread(target=worker)
t2 = threading.Thread(target=worker)
t1.start()
t2.start()
t1.join()
t2.join()
+-----------+ +-----------+ +-----------+
| Process 1 | | Process 2 | | Process 3 |
| | | | | |
| Eigener | | Eigener | | Eigener |
| Speicher | | Speicher | | Speicher |
+-----------+ +-----------+ +-----------+
| | |
+-------- IPC ------+-------- IPC ------+
(z. B. Queues, Pipes, Sockets)
Beispiel:
import multiprocessing
import time
def worker():
print("Prozess startet")
time.sleep(2)
print("Prozess endet")
p1 = multiprocessing.Process(target=worker)
p2 = multiprocessing.Process(target=worker)
p1.start()
p2.start()
p1.join()
p2.join()
| Kriterium | Threads | Processes |
|---|---|---|
| Speicher | Gemeinsamer Speicher | Getrennter Speicher |
| Startzeit | Schnell | Etwas langsamer |
| CPU-bound Performance | Schlecht (wegen GIL) | Gut |
| I/O-bound Performance | Gut | Gut |
| Kommunikation | Einfach (gemeinsamer Speicher) | Komplexer (IPC nötig) |
| Nutzung des GIL | Ja | Nein |
Beispiel:
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
asyncio.run(say_hello())
asyncio.gatherimport asyncio
async def task(n):
print(f"Task {n} startet")
await asyncio.sleep(n)
print(f"Task {n} endet")
return n * 10
async def main():
results = await asyncio.gather(
task(1),
task(2),
task(3)
)
print("Ergebnisse:", results)
asyncio.run(main())
| Kriterium | 🧵 Threading | 💥 Multiprocessing | ⚡ Asyncio |
|---|---|---|---|
| Parallel? | Ja (pseudo) | Ja (echt) | Ja (kooperativ) |
| GIL betroffen? | ✅ Ja | ❌ Nein | ✅ Ja |
| Für CPU-bound? | 🚫 Nein | ✅ Ja | 🚫 Nein |
| Für I/O-bound? | ✅ Ja | 😐 Mäßig geeignet | ✅ Ja |
| Speicher | Gemeinsam | Getrennt | Gemeinsam (1 Thread) |
| Kommunikation | Einfach | Komplex (IPC nötig) | Intern (await/gather) |
| Nutzung von Tasks | Threads | Prozesse | Coroutines |
| Startzeit | Schnell | Langsamer | Sehr schnell |
| Schwierigkeit | 😊 Einfach | 😓 Schwer | 😌 Mittel |