σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyRoadmapsSkills
Ctrl K
AO
+1,2 Tsd. Lernende
AnmeldenBeitreten ↗
RoadmapsSkills
AO
+1,2 Tsd. Lernende
Anmelden
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyRoadmapsSkills
Ctrl K
AO
+1,2 Tsd. Lernende
AnmeldenBeitreten ↗
RoadmapsSkills
AO
+1,2 Tsd. Lernende
Anmelden
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Lock-free-Skill›Modul 2 · Atomics, Ordnung und Garantien›Thema 2.3·~45 Min.Fortgeschritten

Fortschrittsgarantien: von obstruction-free bis wait-free

Position in der SkillModul 2 von 4 · Thema 3 von 3
2.3 Fortschrittsgarantien: von obstruction-free bis wait-free
—Lab-Punkte— / 100XP gesamt—Serie—

4 Min. Lesezeit4 Codebeispiele

  1. 01Ein angehaltener Thread
  2. 02Sperrfrei: Irgendjemand wird immer fertig
  3. 03Wait-free: alle werden fertig
  4. 04Was Sie bauen werden

Drei Zähler liefern unter jedem Schedule die richtige Antwort, und doch sind sie nicht gleich. Halten Sie einen Thread im schlimmsten Moment an: Die Spinlock stoppt alle, der Compare-and-Swap-Zähler lässt jemanden durch, und der Fetch-and-Add-Zähler lässt jeden Thread in 1 Zug fertig werden. Das sind die drei Stufen der Leiter der Fortschrittsgarantien.

01

Ein angehaltener Thread

Ein echtes Betriebssystem kann einen Thread jederzeit von seinem Kern nehmen: Seine Zeitscheibe endet, ein Seitenfehler schickt ihn zur Platte, ein Interrupt kommt. Eine gute nebenläufige Struktur sollte das überstehen. Wir präzisieren die Frage mit einem Gegner. Er führt ein prefix von Zügen seiner Wahl aus, hält dann einen Thread für immer an, und wir geben allen anderen ein budget an Runden. Hat ein nicht angehaltener Thread dann immer noch nicht fertig, kann die Struktur gestoppt werden. Eine Spinlock scheitert an diesem Test auf die einfachste Weise: Hält der Gegner den Thread an, der die Sperre hält, drehen die anderen, bis das Budget endet.

main.pyPython
def locked_increments(k):    def thread(shared):        for _ in range(k):            while True:                yield                if not shared.get("lock", 0):                    shared["lock"] = 1                    break            yield            value = shared.get("x", 0)            yield            shared["x"] = value + 1            yield            shared["lock"] = 0    return threaddef progress_without(make_threads, prefix, paused, budget=50):    n = len(make_threads({}))    allowed = [tid for tid in range(n) if tid not in paused]    _, alive = run_prefix(make_threads, list(prefix) + allowed * budget)    return [tid for tid in allowed if tid not in alive]lk = lambda shared: [locked_increments(1)(shared), locked_increments(1)(shared)]print(run_prefix(lk, [0]))print("thread 0 paused holding the lock:", progress_without(lk, [0], [0]))print("thread 0 finished first:         ", progress_without(lk, [0, 0, 0, 0], [0]))
({'lock': 1}, [0, 1])
thread 0 paused holding the lock: []
thread 0 finished first:          [1]

Nach einem Zug hält Thread 0 die Sperre ('lock': 1). Hält man ihn dort an, beendet Thread 1 in fünfzig Runden nichts: []. Lässt man Thread 0 erst seine vier Züge machen, ist die Sperre wieder frei, und Thread 1 wird fertig: [1]. Der Unterschied ist nur, wo der Gegner zuschlägt. Eine Sperre macht jede Pause im kritischen Abschnitt zu einer Pause des ganzen Programms.

02

Sperrfrei: Irgendjemand wird immer fertig

is_blocking automatisiert den Gegner. Es probiert jedes Präfix von einigen Zügen, hält jeden lebenden Thread der Reihe nach an und fragt progress_without, ob die anderen noch fertig werden. Bei der Spinlock lautet die Antwort schon nach einem einzigen Zug ja. Beim Compare-and-Swap-Zähler nie: Ein Thread, der zwischen Lesen und Tausch angehalten wird, lässt den Wert einfach unverändert, sodass der Tausch des anderen Threads gelingt. Das ist die Definition von sperrfrei: Das Gesamtsystem kommt immer voran, selbst wenn ein Thread für immer angehalten ist.

main.pyPython
def is_blocking(make_threads, depth=3, budget=50):    def visit(prefix):        _, alive = run_prefix(make_threads, prefix)        for paused in alive:            finished = progress_without(make_threads, prefix, [paused], budget)            if len(finished) < len([tid for tid in range(len(make_threads({}))) if tid != paused]):                return True        if len(prefix) < depth:            return any(visit(prefix + [tid]) for tid in alive)        return False    return visit([])cs = lambda shared: [cas_increments(1)(shared), cas_increments(1)(shared)]at = lambda shared: [atomic_increments(1)(shared), atomic_increments(1)(shared)]print("spin lock:", is_blocking(lk))print("compare-and-swap:", is_blocking(cs))print("fetch-and-add:", is_blocking(at))
spin lock: True
compare-and-swap: False
fetch-and-add: False

Sowohl der Compare-and-Swap-Zähler als auch der Fetch-and-Add-Zähler sind nicht blockierend. Die Leiter hat eine dritte Stufe, weil „sperrfrei“ einem Thread noch einen schlechten Tag erlaubt: Er kann jedes Rennen verlieren.

03

Wait-free: alle werden fertig

Eine wait-free-Operation hat eine Schranke für ihre eigenen Züge, die kein Nachbar brechen kann. worst_own_turns misst sie: die größte Zahl von Zügen, die ein Thread über alle vollständigen Schedules nimmt. classify stellt dann die scharfe Frage. Bleibt der schlimmste Fall von Thread 0 gleich, wenn sein Nachbar 1 Inkrement hat und wenn er 3 hat? Wenn ja, ist Thread 0 wait-free. Wenn er wächst, kann der Nachbar Thread 0 öfter wiederholen lassen, die Struktur ist also nur lock-free.

main.pyPython
def worst_own_turns(make_threads, tid):    worst = 0    def visit(prefix):        nonlocal worst        _, alive = run_prefix(make_threads, prefix)        if not alive:            worst = max(worst, prefix.count(tid))            return        for other in alive:            visit(prefix + [other])    visit([])    return worstdef classify(factory, others=(1, 3)):    def program(m):        return lambda shared: [factory(1)(shared), factory(m)(shared)]    if is_blocking(program(others[0])):        return "blocking"    light, busy = worst_own_turns(program(others[0]), 0), worst_own_turns(program(others[1]), 0)    return "wait-free" if light == busy else "lock-free"for neighbour in (1, 2, 3):    cas = lambda shared: [cas_increments(1)(shared), cas_increments(neighbour)(shared)]    atomic = lambda shared: [atomic_increments(1)(shared), atomic_increments(neighbour)(shared)]    print(neighbour, "increments next door: CAS", worst_own_turns(cas, 0), "turns, fetch-and-add", worst_own_turns(atomic, 0))print([classify(f) for f in (locked_increments, cas_increments, atomic_increments)])
1 increments next door: CAS 4 turns, fetch-and-add 1
2 increments next door: CAS 6 turns, fetch-and-add 1
3 increments next door: CAS 8 turns, fetch-and-add 1
['blocking', 'lock-free', 'wait-free']
Welche Wahl

Wait-free ist die stärkste und teuerste Stufe im Entwurf, und Fetch-and-Add zeigt, dass sie nicht immer außer Reichweite ist. Lock-free ist das praktische Ziel für Warteschlangen und Stapel. Eine Sperre ist in Ordnung, wo ein angehaltener Halter nicht schaden kann, aber nie auf einem Pfad, der nicht stocken darf.

main.pyPython
returns = [float(r) for r in load_bars()["close"].pct_change().dropna()][:6]ups = [sum(1 for r in returns[start::2] if r > 0) for start in (0, 1)]for name, factory in (("locked", locked_increments), ("cas", cas_increments), ("atomic", atomic_increments)):    program = lambda shared, f=factory: [f(k)(shared) for k in ups]    blocking = is_blocking(program)    print(name, classify(factory), blocking, None if blocking else worst_own_turns(program, 0))
locked blocking True None
cas lock-free False 8
atomic wait-free False 1
Was die echten Daten sagendie Aufwärtstage der ersten 6 SPY-Renditen: 1 Inkrement für Thread 0, 3 für Thread 1
Spinlockblockingein angehaltener Thread stoppt den anderen
Compare-and-Swaplock-freeschlimmster Fall von Thread 0: 8 Züge
Fetch-and-Addwait-freeschlimmster Fall von Thread 0: 1 Zug
Meiste eigene Züge von Thread 0 je nach Arbeit des Nachbarn
Fetch-and-Add, 3 Inkremente nebenan1
CAS, 1 Inkrement nebenan4
CAS, 2 Inkremente nebenan6
CAS, 3 Inkremente nebenan8

Lesen Sie die Balken: Der Fetch-and-Add-Thread braucht 1 Zug, egal wie beschäftigt sein Nachbar ist, während der Compare-and-Swap-Thread 4, dann 6, dann 8 Züge braucht, je mehr Inkremente der Nachbar macht. Jedes zusätzliche Inkrement nebenan kann Thread 0 ein weiteres Rennen verlieren und Lesen und Tausch wiederholen lassen. Dieses Wachstum trennt genau lock-free von wait-free.

DefinitionFortschrittsgarantien

Blockierend: Ein angehaltener Thread kann die anderen stoppen. Lock-free: Irgendein Thread wird immer fertig. Wait-free: Jeder Thread wird in einer beschränkten Zahl eigener Züge fertig. Jede Stufe schließt die vorige dieser Liste ein.

04

Was Sie bauen werden

Das Labor folgt der Lektion: der Spinlock-Zähler, das Ausführen mit angehaltenen Threads, der Test, ob das Anhalten eines Threads die anderen stoppen kann, der schlimmste Fall der eigenen Züge eines Threads, die Klassifikation eines Zählers und die Studie der drei Zähler an den SPY-Aufwärtstagen.

  • Funktion · FrageBaut auf
  • locked_increments(k)Ein Zähler, der auf eine Sperre wartet?Einfache Arithmetik
  • progress_without(make_threads, prefix, paused, budget)Wer wird fertig, wenn Threads angehalten sind?run_prefix
  • is_blocking(make_threads, depth, budget)Kann ein angehaltener Thread alle stoppen?progress_without
  • worst_own_turns(make_threads, tid)Wie viele Züge kann ein Thread brauchen?run_prefix
  • classify(factory, others)Blockierend, lock-free oder wait-free?is_blockingworst_own_turns
  • study(bars)Wo stehen die drei Zähler bei SPY?classifyis_blockingworst_own_turns

Das Wichtigste

  • Eine Sperre kann alle stoppen. Halten Sie den Halter nach einem Zug an, und der andere Thread beendet in fünfzig Runden nichts.
  • Lock-free heißt, irgendjemand wird immer fertig. Aber ein Thread kann jedes Rennen verlieren: Compare-and-Swap braucht 4, 6, 8 Züge, wenn der Nachbar mehr arbeitet.
  • Wait-free hat eine Schranke, die kein Nachbar bricht. Fetch-and-Add wird in 1 Zug fertig, was der Nachbar auch tut.
Schnelltest
Ein Thread macht ein Inkrement mit Compare-and-Swap, während ein anderer 5 Inkremente macht. Welche Aussage ist garantiert?
Wähle eine Antwort.

Nächstes Thema: Sie wissen jetzt, was eine Warteschlange versprechen könnte. Ein Ringpuffer für einen Producer und einen Consumer baut die einfachste nützliche sperrfreie Warteschlange, mit einem Schreiber und einem Leser, festem Speicher und gar keinem atomaren Read-Modify-Write, und der Explorer prüft, dass sie nie einen Tick verliert oder umordnet.

Lab: Was bewertet wird

6 Aufgaben · 100 Pkt.
  • Ein Zähler auf einer Spinlock10 Pkt.
  • Mit angehaltenen Threads ausführen20 Pkt.
  • Kann eine Pause alle stoppen20 Pkt.
  • Der schlimmste Fall eines Threads15 Pkt.
  • Die Garantie benennen10 Pkt.
  • Die Studie mit der SPY-Tabelle25 Pkt.
Explorer · lab-lf-05
▾ lab-lf-05
▾ data
…
bash · im Browser
learner@quantcraft:~/lab-lf-05$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

Alles in lab-lf-05 wird auf die Startdateien zurückgesetzt und das Terminal geleert. Ihre aktuelle Lösung geht verloren – das lässt sich nicht rückgängig machen.

Wird zurückgesetzt
  • main.py → Startcode
  • Terminal-Verlauf und Testausgabe
  • Nicht eingereichte Änderungen
Bleibt erhalten
  • Beste Lab-Punktzahl
  • Ihre XP und Ihre Serie
  • Vorlesungsfortschritt und Notizen