2.6 KiB
2.6 KiB
Task 16 — Lock und @timer-Decorator
Rückblick Task 15: Concurrency
Ihr habt ThreadPoolExecutor und as_completed eingeführt. Die wichtigsten Punkte:
- I/O-bound vs. CPU-bound: Overpass-Requests sind I/O-bound — die CPU wartet
auf die Netzwerkantwort. Threads sind dafür ideal, weil Python während des
Wartens (I/O) den GIL freigibt und andere Threads laufen lässt. Bei CPU-bound
Tasks (z.B. Bildverarbeitung, ML-Training) hilft Threading nicht —
dort braucht man
multiprocessing. executor.map()vs.as_completed():map()ist einfacher, liefert Ergebnisse aber in der Reihenfolge der Inputs — auch wenn spätere Futures früher fertig sind.as_completed()liefert Ergebnisse sobald sie fertig sind, was bei unterschiedlichen Antwortzeiten effizienter ist und pro Future individuelles Error-Handling erlaubt.all_pois.extend()aus mehreren Threads: In Python istlist.extend()durch den GIL (Global Interpreter Lock) de facto atomar für einfache Operationen — ein echter Race Condition-Crash ist unwahrscheinlich. Aber: die Reihenfolge der Ergebnisse ist nicht deterministisch, und bei komplexeren Operationen (read-modify-write) wäre ein Lock nötig.
Aufgabe
Zwei Erweiterungen stehen an — eine zur Illustration von Thread-Safety, eine zur Laufzeitmessung.
Teil A — FetchMode.CONCURRENT_LOCKED:
Der bisherige CONCURRENT-Modus sammelt Ergebnisse ohne explizite
Synchronisation. Füge einen dritten Modus hinzu, der zeigt, wie man
all_pois.extend() mit einem Lock absichert.
- Ergänze
FetchModeumCONCURRENT_LOCKED = "concurrent_locked". - Implementiere den neuen Modus in
fetch_and_store()analog zuCONCURRENT, aber mit einemthreading.Lock:
with lock:
all_pois.extend(future.result())
- Ergänze
config.yaml— setzefetch_mode: concurrent_locked.
Teil B — @timer-Decorator:
-
Lege eine neue Datei
utils.pyan. -
Schreibe darin einen
@timer-Decorator, der die Laufzeit einer Funktion misst und perlogger.info()ausgibt.def timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = time.perf_counter() - start logger.info(f"[timer] {func.__name__} → {elapsed:.2f}s") return result return wrapper -
Dekoriere
main()inmain.pymit@timer.
Fragen zum Nachdenken:
list.extend()ist in CPython durch den GIL geschützt — warum empfiehlt es sich trotzdem, einen Lock zu verwenden?