R

ReflexKR

Reflex-Dokumentation

Nebenläufigkeit in der Nachtküche

·

Kursüberblick

1

Die Schlange am ersten Abend: Nebenläufigkeit und Parallelität

2

Mehr Köche einstellen: Grundlagen von threading

3

Die Zahl auf der Tafel stimmt nicht: Race Conditions und Lock

4

Nur drei Herdplatten: Synchronisationswerkzeuge

5

Messer und Bretter: Deadlock

6

Die Bonschiene: queue

7

Leihköche: concurrent.futures

8

Warum der Teig nicht schneller wird: der GIL und Parallelität

9

Eine Servicechefin für alle Tische: erste Schritte mit asyncio

10

Die Gruppenreservierung platzt: strukturierte Nebenläufigkeit

11

Der Gastraum steht still: blockierende Aufrufe und Kombinieren

12

Die Nacht der Kritikerin: alles zusammen

Nebenläufigkeit in der Nachtküche
Lektion 8

Zeit 5:50–6:40

Warum der Teig nicht schneller wird: der GIL und Parallelität

Der Bon für heute Nacht

#08 · Achte Nacht

Warum der Teig nicht schneller wird

Problem

Vier Klumpen Nudelteig für morgen. Vier Köche sind darauf angesetzt, und es dauert genauso lange, als würde einer allein kneten.

Ziel

Die vier Teigklumpen mindestens doppelt so schnell fertig bekommen wie allein.

Nachtlokal Dongsi

Ein Uhr früh, der Service ist vorbei. Auf der Arbeitsfläche liegen vier Klumpen Nudelteig für morgen. Minho, Sua, Herr Tak und du. Jeder hat einen Klumpen übernommen, also müsste es viermal so schnell gehen.

Doch der Timer der Chefin bleibt bei fast derselben Zahl stehen wie beim Kneten allein. Minho legt den Kopf schief. „Wir hatten doch alle die Hände voll?“

Chefin Yoon Seoyeon zeigt auf den Messerblock auf der Arbeitsfläche. Darin steckt genau ein Messer. „In dieser Küche gibt es eine alte Regel. Schneiden und kneten darf immer nur einer. Heute reden wir über diese Regel.“

8-1. Vier Köche, die Zeit bleibt gleich (5 Min.)

Die Bestellungen in Lektion 1 waren Warten, bis die Suppe kocht. Der Teig heute ist anders. Eine Schleife, die zwei Millionen Mal Zahlen addiert: Arbeit, bei der die Hände nie stillstehen (CPU-Arbeit).

import threading
import time

def knead(n=2_000_000):
    s = 0
    for i in range(n):   # ununterbrochen rechnen: Teig kneten
        s += i
    return s

start = time.perf_counter()
for _ in range(4):
    knead()
print(f"sequential {time.perf_counter() - start:.2f}s")

start = time.perf_counter()
cooks = [threading.Thread(target=knead) for _ in range(4)]
for th in cooks:
    th.start()
for th in cooks:
    th.join()
print(f"4 threads  {time.perf_counter() - start:.2f}s")
# sequential 0.24s
# 4 threads  0.23s   (12-Kern-PC, gemessen mit Python 3.14)
  • Obwohl wirklich vier Threads liefen, dauert es fast genauso lange wie nacheinander.
  • Der PC hat 12 Kerne. An zu wenigen Kernen liegt es nicht.
  • Die Zahlen schwanken je nach PC und Lauf etwas. Entscheidend ist, dass die beiden Zahlen fast gleich sind.

8-2. Der GIL: nur ein Messer (8 Min.)

CPython (das ganz normale Python, das wir verwenden) hat einen GIL (Global Interpreter Lock). Es gibt genau eine Regel:

In einem Prozess führt immer nur ein Thread gleichzeitig Python-Bytecode aus.

In der Küche heißt das: „Es gibt nur ein Messer.“ Auch mit vier Köchen kann nur schneiden, wer das Messer hält; die anderen warten, bis es zurückkommt. Das Messer wandert etwa alle 0,005 Sekunden zum Nächsten (sys.getswitchinterval(), Lektion 3).

ZeitMinhoSuaHerr TakDu
0–5 msknetet mit dem Messerwartetwartetwartet
5–10 mswartetknetet mit dem Messerwartetwartet
10–15 mswartetwartetknetet mit dem Messerwartet
15–20 mswartetwartetwartetknetet mit dem Messer

Die vier kneten abwechselnd, also kommt jeder Klumpen ein bisschen voran. Nebenläufigkeit ja, Parallelität nein (Lektion 1). Die Gesamtarbeit bleibt gleich, also bleibt auch die Zeit gleich.

Warum gibt es diese Regel? Python zählt bei jedem Objekt, „an wie vielen Stellen es gerade benutzt wird“ (Referenzzähler). Ändern mehrere Threads diese Zahl gleichzeitig, geht sie kaputt wie die Tafel aus Lektion 3. Vor langer Zeit hat CPython den einfachen Weg gewählt, den ganzen Interpreter mit einem einzigen Lock zu schützen, und das ist der GIL.

Wer wartet, legt das Messer ab

Der GIL hat eine wichtige Ausnahme. Während ein Thread wartet (time.sleep, Dateien lesen, Netzwerkantworten), gibt er den GIL ab. Wer darauf wartet, dass die Suppe kocht, braucht kein Messer.

def simmer():
    time.sleep(0.3)   # warten, bis die Suppe kocht: währenddessen wird der GIL abgegeben

start = time.perf_counter()
cooks = [threading.Thread(target=simmer) for _ in range(4)]
for th in cooks:
    th.start()
for th in cooks:
    th.join()
print(f"4 threads {time.perf_counter() - start:.2f}s")
# 4 threads 0.30s   (nacheinander wären es 1,2 Sekunden)

Auch in C geschriebene Funktionen können den GIL während schwerer Berechnungen abgeben. So macht es hashlib, wenn es große Datenmengen hasht. Das erledigt nicht eine menschliche Hand, sondern die Knetmaschine (C-Code), und sie steht damit außerhalb der Messerregel.

import hashlib

dough = b"x" * 50_000_000   # 50 MB

def press():
    hashlib.sha256(dough).hexdigest()   # während der C-Code rechnet, wird der GIL abgegeben

# genauso gemessen wie oben (auf diesem PC ungefähr)
# sequential 0.11s
# 4 threads  0.04s
Art der ArbeitMit Threads und GILGrund
Warten (sleep, Dateien, Netzwerk)wird schnellerbeim Warten wird der GIL abgegeben
schwere Berechnungen in C-Erweiterungen (hashlib, zlib usw.)meist schnellerder C-Code gibt den GIL ab und rechnet
reine Python-Berechnungen (Schleifen, Strings zusammensetzen)wird nicht schnellerBytecode läuft immer nur in einem Thread

Und der GIL schützt deinen Code nicht. Er schützt nur das Innere des Interpreters; mehrstufige Vorgänge wie count += 1 werden mittendrin unterbrochen. Deshalb lag die Tafel in Lektion 3 trotz GIL falsch.

Der Versuchsaufbau unten ist eine Küche, in der auf dem Produktionsserver echte Threads time.sleep aufrufen. Der Server läuft mit einem Python mit GIL, und trotzdem überlappen sich die Balken, weil es Warten ist.

Küchen-Zeitleiste

Bestellungen

6

Köche

3

Kochen

Warten

Abgebrochen

Fehlgeschlagen

4 Wartearbeiten zu je 0,3 s. Selbst auf einem Server mit GIL überlappen sich die Balken mit mehreren Köchen, und nach etwa 0,3 s ist alles fertig. Vergleiche mit „Allein, eins nach dem anderen“.

Tipp der Chefin

Wenn du mehr Threads hast und es trotzdem nicht schneller wird, frag dich zuerst: Ist das Suppe oder Teig? Bei Suppe reichen Threads. Bei Teig brauchst du nicht mehr Leute, sondern mehr Küchen.

8-3. Die Filialküche: multiprocessing (7 Min.)

Wenn es nur ein Messer gibt, richtest du einfach noch eine Küche mit eigenem Messer ein. Das ist ein Prozess. Jeder Prozess hat seinen eigenen Python-Interpreter und auch seinen eigenen GIL.

import multiprocessing as mp
import os

def branch(name):
    print(name, "pid", os.getpid(), "parent", os.getppid())

if __name__ == "__main__":
    print("main pid", os.getpid())
    p = mp.Process(target=branch, args=("branch-1",))
    p.start()
    p.join()
    print("exitcode", p.exitcode)
# zum Beispiel:
# main pid 39516
# branch-1 pid 40268 parent 39516
# exitcode 0

Process benutzt man genau wie threading.Thread (target, args, start, join). Der Unterschied ist os.getpid(), die Prozessnummer. Die Filiale ist ein eigenständiges Programm mit einer anderen Nummer als das Stammhaus.

Sogar der Kühlschrank ist getrennt

Die Filiale teilt keinen Speicher. Was die Filiale auch in eine Liste steckt, die Liste im Stammhaus bleibt unverändert.

orders = []

def add(n):
    orders.append(n)
    print("in branch:", orders)

if __name__ == "__main__":
    p = mp.Process(target=add, args=("#1",))
    p.start()
    p.join()
    print("in main:", orders)
# in branch: ['#1']
# in main: []

Das Tafel-Problem aus Lektion 3 entsteht so gar nicht erst, dafür musst du dir Ergebnisse ausdrücklich zurückgeben lassen. Wenn du mehrere Aufgaben verteilen willst, ist Pool praktisch.

def knead(n):
    s = 0
    for i in range(n):
        s += i
    return s

if __name__ == "__main__":
    with mp.Pool(processes=4) as pool:
        print(pool.map(knead, [10, 100, 1000, 10_000]))
# [45, 4950, 499500, 49995000]

Warum if __name__ == "__main__": unverzichtbar ist

Windows und macOS eröffnen Filialen per spawn. Dabei wird ein neues Python gestartet, und dieses Python importiert deine Datei noch einmal von vorn. Es muss die Funktionsdefinitionen neu lesen, um zu wissen, was knead ist.

Fehlt diese Prüfung, stößt die Filiale beim Lesen der Datei wieder auf die Zeile mp.Process(...).start() und versucht, selbst eine Filiale zu eröffnen. Python verhindert das und meldet einen Fehler.

# ohne Prüfung direkt auf oberster Ebene gestartet (Windows)
p = mp.Process(target=knead, args=(1,))
p.start()
# in der Filiale:
# RuntimeError:
#         An attempt has been made to start a new process before the
#         current process has finished its bootstrapping phase.
#         ...

Deshalb speicherst du multiprocessing-Beispiele als Datei und startest sie mit python datei.py. In einer interaktiven Shell oder einem Notebook findet die Filiale die Funktion oft nicht und scheitert.

8-4. Leihköche in die Filiale schicken: ProcessPoolExecutor (6 Min.)

Erinnerst du dich an ThreadPoolExecutor aus Lektion 7? ProcessPoolExecutor gibt Aufgaben auf genau dieselbe Weise an Prozesse ab. submit, Future, result(), map und as_completed sind alle gleich.

import os
from concurrent.futures import ProcessPoolExecutor

def knead(n):
    s = 0
    for i in range(n):
        s += i
    return os.getpid(), s

if __name__ == "__main__":
    print("main", os.getpid())
    with ProcessPoolExecutor(max_workers=2) as ex:
        future = ex.submit(knead, 1000)
        print(future.result())
        for pid, s in ex.map(knead, [10, 100, 1000]):
            print(pid, s)
# zum Beispiel:
# main 43452
# (42784, 499500)
# 42784 45
# 42784 4950
# 42784 499500

Die Prozessnummer ist eine andere als bei main. Welche Filiale welchen Teig übernimmt, ändert sich von Lauf zu Lauf.

Was hin- und hergeht, muss sich picklen lassen

Filiale und Stammhaus teilen keinen Speicher, also werden Funktion, Argumente und Ergebnisse in Bytes verpackt (pickle) verschickt und empfangen. Was sich nicht verpacken lässt, kann nicht verschickt werden. Das typische Beispiel ist ein Lambda.

def double(x):
    return x * 2

if __name__ == "__main__":
    with ProcessPoolExecutor(max_workers=2) as ex:
        print(ex.submit(double, 21).result())
        try:
            ex.submit(lambda x: x * 2, 21).result()
        except Exception as e:
            print(type(e).__name__, e)
# 42
# PicklingError Can't pickle <function <lambda> at 0x...>: it's not found as __main__.<lambda>
  • Funktionen werden über ihren Namen verpackt. Die Filiale sucht sich „das double aus der Datei“ und benutzt es. Ein Lambda ohne Namen kann die Filiale nicht finden.
  • Funktionen, die du an eine Filiale gibst, definierst du deshalb mit def auf oberster Ebene der Datei. Funktionen in Funktionen und Lambdas gehen nicht.
  • Auch offene Dateien, Locks oder Sockets kannst du nicht als Argument mitschicken. Tausche einfache Werte aus: Zahlen, Strings, Listen, dicts.

8-5. Was es kostet, eine Filiale zu eröffnen (4 Min.)

Eine Filiale gibt es nicht umsonst. Ein neues Python muss starten, die Datei muss neu importiert werden, und die Werte müssen verpackt hin- und hergehen. Ist der Teig klein, dauert diese Vorbereitung länger als die Arbeit selbst.

# die vier kleinen Teige aus 8-1 (je zwei Millionen) auf 4 Prozesse
with ProcessPoolExecutor(max_workers=4) as ex:
    results = list(ex.map(knead, [2_000_000] * 4))
# 4 processes 0.31s   (gemessen auf einem 12-Kern-PC, nacheinander 0.24 s)
Methode (4 kleine Teige)Zeit (gemessen auf einem 12-Kern-PC)
nacheinander0,24 s
4 Threads0,23 s
4 Prozesse0,31 s

Es ist sogar langsamer geworden. Macht man den Teig zehnmal so groß (zwanzig Millionen), sieht es anders aus. Auf diesem PC dauerte es nacheinander ungefähr 2,4–2,7 Sekunden, mit 4 Prozessen 0,9–1,1 Sekunden. Die Vorbereitungskosten bleiben fast gleich, aber die Arbeit ist größer geworden.

  • Prozesse lohnen sich für Berechnungen, bei denen jedes Stück groß genug ist. Wenn du tausende kleine Aufgaben verschickst, bündle mehrere und schick sie auf einmal (chunksize= bei map).
  • Leg einen Pool einmal an und benutze ihn mehrfach. Wer für jede Aufgabe ein neues with ProcessPoolExecutor() öffnet, richtet jedes Mal eine neue Filiale ein.

8-6. Drei Arten, eine Filiale zu eröffnen: spawn, fork, forkserver (3 Min.)

Wie eine Filiale eingerichtet wird, nennt man die Startmethode (start method).

MethodeWas sie tutIn der Küche
spawnstartet ein neues Python und importiert die Datei neurichtet in einem leeren Gebäude eine Küche von Grund auf ein
forkkopiert den aktuellen Prozess komplettkopiert das Stammhaus, wie es ist (samt der Locks der Köche, die gerade arbeiten)
forkserverhält einen sauberen Serverprozess bereit und kopiert von dorthält eine saubere Musterküche bereit und kopiert die
  • Seit 3.14 ist unter Linux forkserver der Standard. Unter Windows und macOS ist es spawn. Der frühere Linux-Standard fork wurde abgelöst, weil sich beim Kopieren eines Programms mit laufenden Threads die Locks verheddern können.
  • Unter Windows gibt es nur spawn.
import multiprocessing as mp

if __name__ == "__main__":
    print(mp.get_start_method())        # spawn   (Windows)
    print(mp.get_all_start_methods())   # ['spawn']

    ctx = mp.get_context("spawn")       # nur dieser Pool mit spawn
    with ctx.Pool(processes=2) as pool:
        print(pool.map(knead, [10, 100]))   # [45, 4950]
  • mp.get_context("spawn") gilt nur für Pool und Process, die du aus diesem Kontext erzeugst. In Bibliotheken ist das der sichere Weg.
  • Um das ganze Programm umzustellen, rufst du ganz am Anfang von if __name__ == "__main__": genau einmal mp.set_start_method("spawn") auf. Ein zweiter Aufruf ergibt RuntimeError: context has already been set.
  • Der Standard unterscheidet sich je nach Betriebssystem. Wenn dein Code überall gleich laufen muss, gib spawn ausdrücklich an und teste damit.

8-7. Umbau: Python 3.14t ohne GIL (5 Min.)

Seit 3.14 wird der Free-Threading-Build offiziell unterstützt. Das ist ein Python ohne GIL, die ausführbare Datei heißt python3.14t (mit t am Ende). Die Küche wurde umgebaut, und jetzt gibt es so viele Messer wie Köche.

  • Installiere im Installer von python.org die Free-Threading-Binärdateien mit, oder starte mit uv per uv run --python 3.14t datei.py.
  • Ob der GIL gerade an ist, prüfst du mit sys._is_gil_enabled().
# knead_threads.py: über den Code aus 8-1 nur zwei Zeilen setzen
import sys
print(sys._is_gil_enabled())
...

# python knead_threads.py
# True
# sequential 0.24s
# 4 threads  0.23s

# python3.14t knead_threads.py
# False
# ...
# 4 threads  0.09s   (gemessen auf einem 12-Kern-PC, etwa 2,7-mal so schnell)

Am Code wurde kein einziges Zeichen geändert. Derselbe Thread-Code wurde nur durch den Wechsel des Interpreters etwa 2,7-mal schneller. Dass es nicht viermal so schnell ist, liegt an den Kosten für Starten und Verwalten der Threads und an den internen Mechanismen, die ohne GIL für Sicherheit sorgen. Dafür kann Code mit nur einem Thread etwas langsamer sein als im normalen Build.

Zwei Dinge solltest du wissen.

  • C-Erweiterungen können den GIL wieder einschalten. Importierst du ein Erweiterungsmodul, das nicht als Free-Threading-kompatibel gekennzeichnet ist, gibt Python eine Warnung aus und schaltet den GIL wieder ein. Mit der Umgebungsvariable PYTHON_GIL=0 (oder -X gil=0) kannst du ihn zwangsweise abschalten; mit PYTHON_GIL=1 ist der GIL auch unter 3.14t an.
  • Race Conditions treten häufiger auf. Das Tafel-Experiment aus Lektion 3 ergab unter 3.14t 2410 von 4000 (gemessen). Wenn wirklich alles gleichzeitig läuft, werden Locks noch wichtiger.

Die vier oberen Balken im Versuchsaufbau unten wurden bei der Vorbereitung des Kurses auf einem 12-Kern-PC gemessen. Darunter lässt „Auf dem Server kneten“ den Teig (600.000 × 4) direkt auf dem Produktionsserver dieser Seite laufen, einmal nacheinander, einmal mit 4 Threads. Der Produktionsserver ist ein Build mit GIL, und seine Kernzahl ist vorher nicht bekannt, also können die Zahlen von den gemessenen Balken abweichen. Achte darauf, ob die beiden Balken ähnlich lang sind.

Teig-Vergleich

Lokal gemessen (12-Kern-PC, Python 3.14)

Nacheinander

0.24s

4 Threads

0.23s

4 Prozesse

0.31s

4 Threads (3.14t, ohne GIL)

0.09s

Gemessene Balken für 4 CPU-Aufgaben: nacheinander, mit Threads, mit Prozessen und mit Threads unter 3.14t ohne GIL. Lass es auch direkt auf dem Server laufen und sieh dir den GIL-Status und die Kernzahl des Servers an.

Tipp der Chefin

Ohne GIL ist die Messerregel weg, aber nicht die Tafelregel. Wenn mehrere an dieselbe Tafel schreiben, schreiben sie jetzt wirklich gleichzeitig. Bevor du auf 3.14t umziehst, prüf zuerst deine Locks noch mal.

8-8. Die Küche mit Trennwänden: Subinterpreter (2 Min.)

Filialen sind teuer, und beim Umbau sind noch nicht alle Bibliotheken mitgezogen. Dazwischen gibt es noch eine Möglichkeit: eine Küche im selben Gebäude, durch Trennwände abgeteilt, also Subinterpreter. In einem Prozess laufen mehrere Interpreter, und jedes Abteil hat seinen eigenen GIL (sein eigenes Messer). Mit 3.14 sind sie als Modul concurrent.interpreters in die Standardbibliothek gekommen.

from concurrent import interpreters

booth = interpreters.create()
booth.exec("menu = ['#1', '#2']; print('booth menu:', menu)")
booth.exec("menu.append('#3'); print('booth menu:', menu)")
print("main has menu?", "menu" in globals())

def knead(n):
    s = 0
    for i in range(n):
        s += i
    return s

print(booth.call(knead, 1000))
booth.close()
# booth menu: ['#1', '#2']
# booth menu: ['#1', '#2', '#3']
# main has menu? False
# 499500
  • Mit create() legst du ein Abteil an, mit exec(code_als_string) führst du darin Code aus, und mit call(funktion, argument) rufst du eine Funktion auf und bekommst das Ergebnis.
  • Variablen im Abteil (menu) gibt es nur im Abteil. Wie bei der Filiale wird kein Speicher geteilt.

In der Praxis ist der InterpreterPoolExecutor (3.14) bequem, der genauso aussieht wie in Lektion 7.

from concurrent.futures import InterpreterPoolExecutor

if __name__ == "__main__":
    with InterpreterPoolExecutor(max_workers=4) as ex:
        results = list(ex.map(knead, [2_000_000] * 4))
# auf diesem PC ungefähr 0.18 s (3.14 mit GIL, nacheinander 0.24 s)

Weil jedes Abteil sein eigenes Messer hat, kommen die Teige auch im normalen Python parallel voran. Denk aber daran, dass viele C-Erweiterungen Subinterpreter noch nicht unterstützen.

Küchenschluss (10 Min.)

Das Ziel für heute Nacht war, die vier Teigklumpen mindestens doppelt so schnell fertig zu bekommen wie allein. Es sind reine Python-Berechnungen, also helfen Threads nicht; und jeder Klumpen ist groß, also lohnen sich die Kosten für das Eröffnen von Filialen.

import time
from concurrent.futures import ProcessPoolExecutor

BIG = 20_000_000   # ein großer Teigklumpen

def knead(n):
    s = 0
    for i in range(n):
        s += i
    return s

if __name__ == "__main__":
    start = time.perf_counter()
    alone = [knead(BIG) for _ in range(4)]
    print(f"sequential {time.perf_counter() - start:.2f}s")

    start = time.perf_counter()
    with ProcessPoolExecutor(max_workers=4) as ex:
        branches = list(ex.map(knead, [BIG] * 4))
    print(f"4 processes {time.perf_counter() - start:.2f}s", branches == alone)
# zum Beispiel (auf diesem PC):
# sequential 2.43s
# 4 processes 0.93s True

Etwa 2,5-mal so schnell. Und das Ergebnis ist exakt dasselbe wie allein. Mit 3.14t erreichst du einen ähnlichen Effekt, wenn du knead mit vier Threads laufen lässt.

WerkzeugMesser (GIL)SpeicherStartkostenPasst zu
Threads (3.14)einer pro ProzessgemeinsamgeringWarten
Threads (3.14t)keinergemeinsamgeringWarten + Rechnen
Prozesseeiner pro Filialegetrennt (pickle)hochgroße Berechnungen
Subinterpretereiner pro AbteilgetrenntmittelRechnen, reiner Python-Code

Die Chefin bindet ihre Schürze ab. „Den Teig haben wir mit mehr Küchen gelöst. Aber was ist mit dem Gastraum? Stellen wir für zehn Tische zehn Leute ein, oder lassen wir eine Person alle zehn Tische betreuen?“

Schichtbuch — Achte Nacht. Mit Threads wurde der Teig nicht schneller, weil es nur ein Messer gibt. Auf vier Filialen verteilt, 2,5-mal so schnell fertig. Morgen wird der Gastraum umgebaut.

Übungen

  1. Ersetze in knead aus 8-1 die Schleife durch time.sleep(0.06) und miss noch einmal nacheinander und mit 4 Threads. Erkläre mit dem GIL, warum Threads diesmal schneller sind.
  2. Ändere die Teiggröße aus 8-5 auf 200.000, zwei Millionen und zwanzig Millionen und trag die Zeiten für nacheinander und für ProcessPoolExecutor in eine Tabelle ein. Ab welcher Größe gewinnen auf deinem PC die Prozesse?
  3. Übergib per submit eine Funktion, die innerhalb einer anderen Funktion definiert ist (verschachtelte Funktion), an ProcessPoolExecutor. Sieh dir an, welcher Fehler kommt, und behebe ihn.

Rezeptkarte der Chefin

  • GIL: Python-Bytecode läuft immer nur in einem Thread gleichzeitig. Beim Warten (I/O) und in manchem C-Code wird der GIL abgegeben.

  • Rechenarbeit (CPU) wird mit Threads nicht schneller → Prozesse, 3.14t, Subinterpreter.

  • ProcessPoolExecutor benutzt man wie ThreadPoolExecutor. if __name__ == "__main__": ist Pflicht.

  • Was mit der Filiale ausgetauscht wird, läuft durch pickle:keine Lambdas, keine verschachtelten Funktionen. def auf oberster Ebene.

  • Prozesse sind teuer zu starten: nur für große Berechnungen. Startmethode: Windows und macOS spawn, Linux ab 3.14 forkserver.

  • sys._is_gil_enabled(): unter 3.14t (python3.14t) False. Derselbe Thread-Code läuft dann parallel.

  • concurrent.interpreters: create() → exec() / call(), InterpreterPoolExecutor (3.14).

← Zurück

Lektion 7 · Leihköche: concurrent.futures

Weiter →

Lektion 9 · Eine Servicechefin für alle Tische: erste Schritte mit asyncio

Über neue Lektionen informiert werden

Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.

Erhoben: deine E-Mail-Adresse · Zweck: Hinweise auf neue Lektionen oder den Start dieses Kurses · Speicherdauer: bis du die Löschung verlangst. Keine andere Nutzung, keine Weitergabe. Antworte einfach auf eine Benachrichtigung, dann löschen wir sie sofort.

Fragen & Antworten

Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.

Wird als reiner Text angezeigt, ohne Links oder Formatierung.

R

ReflexKR

Python und Reflex zum Anfassen. Alle Beispiele wurden mit Reflex 0.10.0 ausgeführt und geprüft.

ReflexKR ist kostenlos und ohne Werbebanner. Wenn es dir geholfen hat, kannst du es mit einem Kaffee unterstützen. ☕ Spendier uns einen Kaffee

Hinweis zu Affiliate-Links
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex