σμ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.2·~1 Std. 15 Min.Fortgeschritten

Das Speichermodell: Acquire und Release

Position in der SkillModul 2 von 4 · Thema 2 von 3
2.2 Das Speichermodell: Acquire und Release
—Lab-Punkte— / 100XP gesamt—Serie—

5 Min. Lesezeit4 Codebeispiele

  1. 01Prozessoren ordnen um
  2. 02Alle Reihenfolgen ausführen
  3. 03Release und Acquire gehören zusammen
  4. 04Was Sie bauen werden

Ein Prozessor darf zwei unabhängige Lese- und Schreibzugriffe in beliebiger Reihenfolge ausführen, und ein anderer Kern kann das Ergebnis dieser Wahl sehen. Veröffentlichen Sie einen SPY-Preis und setzen Sie dann ein Flag: Ein Leser, der das Flag sieht, kann trotzdem einen Preis von 0 lesen. Von den vier Möglichkeiten, Schreiber und Leser zu ordnen, ist nur eine sicher: Release beim Schreiber gepaart mit Acquire beim Leser.

01

Prozessoren ordnen um

Store-Puffer, Caches und der Compiler lassen einen Kern einen späteren Schreib- oder Lesezugriff vor einem früheren beenden, solange ein einzelner Thread es nicht merkt. Ein anderer Thread merkt es. Unsere Maschine modelliert das direkt: Ein Thread ist eine Liste von Operationen und darf jede Operation in beliebiger Reihenfolge ausführen, deren Ordnungsbedingungen erfüllt sind. Operationen auf derselben Adresse behalten immer ihre Reihenfolge, sonst würde ein Thread seine eigenen Schreibzugriffe nicht sehen. Ein Acquire-Load blockiert alles danach; ein Release-Store wartet auf alles davor. Zwei Funktionen sagen alles: must_wait listet auf, worauf eine Operation wartet, und eligible listet auf, was als Nächstes laufen darf.

main.pyPython
def must_wait(ops, j):    waits = []    for i in range(j):        same = ops[i][1] == ops[j][1]        after_acquire = ops[i][0] == "load" and ops[i][3] == "acquire"        release = ops[j][0] == "store" and ops[j][3] == "release"        if same or after_acquire or release:            waits.append(i)    return waitsdef eligible(ops, done):    return [j for j in range(len(ops)) if j not in done and all(i in done for i in must_wait(ops, j))]writer = [("store", "data", 1, "relaxed"), ("store", "flag", 1, "release")]reader = [("load", "flag", "r1", "acquire"), ("load", "data", "r2", "relaxed")]print(must_wait(writer, 1), eligible(writer, set()), eligible(writer, {0}))print(must_wait(reader, 1), eligible(reader, set()))print(eligible([("load", "flag", "r1", "relaxed"), ("load", "data", "r2", "relaxed")], set()))
[0] [0] [1]
[0] [0]
[0, 1]

Der Release-Store des Flags wartet auf Position 0, den Daten-Store, der Schreiber kann seine Operationen also nur der Reihe nach ausführen. Der Acquire-Load des Flags blockiert den dahinter liegenden Daten-Load, der Leser läuft also ebenfalls der Reihe nach. Bei einfachen Loads sind beide gleichzeitig zulässig, und der Leser darf die Daten zuerst lesen.

02

Alle Reihenfolgen ausführen

outcomes ist der Explorer der letzten Themen für diese neue Maschine. Ein Zustand ist, welche Operationen jeder Thread erledigt hat, der Speicher und die Register. Von einem Zustand aus probiert er jede zulässige Operation jedes Threads, und wenn nichts mehr zu laufen ist, sind die Register ein Ergebnis. Weil viele Reihenfolgen denselben Zustand erreichen, merkt er sich bereits gesehene Zustände. Der Message-Passing-Test veröffentlicht mit zwei Stores, erst den Daten, dann dem Flag, und konsumiert mit zwei Loads, erst dem Flag, dann den Daten. Der Konsument liegt falsch, wenn er r1 == 1 (das Flag) und r2 == 0 (noch keine Daten) sieht, was reads_stale prüft.

main.pyPython
def outcomes(programs, memory=None):    found = set()    seen = set()    def visit(done, mem, regs):        state = (tuple(frozenset(d) for d in done), tuple(sorted(mem.items())), tuple(sorted(regs.items())))        if state in seen:            return        seen.add(state)        moved = False        for tid, ops in enumerate(programs):            for j in eligible(ops, done[tid]):                moved = True                kind, address, x, _ = ops[j]                mem2, regs2 = dict(mem), dict(regs)                if kind == "store":                    mem2[address] = x                else:                    regs2[x] = mem.get(address, 0)                done2 = [set(d) for d in done]                done2[tid].add(j)                visit(done2, mem2, regs2)        if not moved:            found.add(tuple(sorted(regs.items())))    visit([set() for _ in programs], dict(memory or {}), {})    return founddef mp_programs(write_order, read_order):    return [        [("store", "data", 1, "relaxed"), ("store", "flag", 1, write_order)],        [("load", "flag", "r1", read_order), ("load", "data", "r2", "relaxed")],    ]def reads_stale(results):    return any(dict(outcome).get("r1") == 1 and dict(outcome).get("r2") == 0 for outcome in results)for pair in (("relaxed", "relaxed"), ("release", "acquire")):    results = outcomes(mp_programs(*pair))    print(pair, sorted(results), "stale:", reads_stale(results))
('relaxed', 'relaxed') [(('r1', 0), ('r2', 0)), (('r1', 0), ('r2', 1)), (('r1', 1), ('r2', 0)), (('r1', 1), ('r2', 1))] stale: True
('release', 'acquire') [(('r1', 0), ('r2', 0)), (('r1', 0), ('r2', 1)), (('r1', 1), ('r2', 1))] stale: False

Bei Relaxed-Ordnung kann der Konsument mit allen vier Paaren (r1, r2) enden, auch (1, 0), dem veralteten. Mit Release auf dem Flag-Store und Acquire auf dem Flag-Load bleiben nur drei: Der Leser, der das Flag sieht, sieht auch die Daten, weil keine Seite über das Flag hinweg umgeordnet werden kann.

03

Release und Acquire gehören zusammen

Reicht eine Seite? Probieren Sie alle vier Paare. Release allein beim Schreiber scheitert: Der Leser kann die Daten vor dem Flag laden. Acquire allein beim Leser scheitert: Der Schreiber kann das Flag vor den Daten speichern. Nur das Paar funktioniert, wie die Tabelle unten zeigt. Das ist der Release-Acquire-Vertrag von C++ std::atomic und Rust, und der Grund, warum eine sperrfreie Warteschlange einen Slot mit einem Release-Store eines Index veröffentlicht und mit einem Acquire-Load liest.

main.pyPython
PAIRS = [("relaxed", "relaxed"), ("release", "relaxed"), ("relaxed", "acquire"), ("release", "acquire")]def ordering_table():    return {pair: reads_stale(outcomes(mp_programs(*pair))) for pair in PAIRS}for pair, stale in ordering_table().items():    print("write", pair[0], "| read", pair[1], "->", "can read stale data" if stale else "safe")
write relaxed | read relaxed -> can read stale data
write release | read relaxed -> can read stale data
write relaxed | read acquire -> can read stale data
write release | read acquire -> safe
Acquire und Release sind ein Paar, kein Zauberwort

Ein Release ohne passendes Acquire auf derselben Variablen schützt nichts. Eine echte C++-Speicherordnung wie seq_cst ist stärker und kostet mehr; relaxed ist die billigste und verspricht gar keine Ordnung. Wählen Sie die schwächste Ordnung, mit der Ihr Test oben besteht, und belegen Sie es mit einem Explorer.

main.pyPython
price = int(round(float(load_bars()["close"].iloc[0]) * 100))programs = mp_programs("release", "acquire")programs[0][0] = ("store", "data", price, "relaxed")print(price, sorted(outcomes(programs)))
9369 [(('r1', 0), ('r2', 0)), (('r1', 0), ('r2', 9369)), (('r1', 1), ('r2', 9369))]
Was die echten Daten sagender erste SPY-Schlusskurs, als 9.369 Cent über ein Flag veröffentlicht
Relaxed + Relaxed4Ergebnisse, veralteter Preis möglich
Nur Release4Ergebnisse, veralteter Preis möglich
Nur Acquire4Ergebnisse, veralteter Preis möglich
Release + Acquire3Ergebnisse, nie veraltet

Mit dem ersten SPY-Schlusskurs, 9.369 Cent, kann der Leser in drei der vier Konfigurationen das Flag und einen Preis von 0 sehen. Ein solcher Fehler besteht alle Tests auf einer Maschine, die zufällig die Ordnung hält, und tritt auf einer anderen auf. Darum muss das sichere Paar über alle erlaubten Reihenfolgen bewiesen werden, wie es der Explorer tut.

DefinitionRelease-Acquire-Paar

Ein Release-Store eines Flags und ein Acquire-Load, der es liest. Alles, was der Schreiber vor dem Release getan hat, ist für den Leser nach dem Acquire sichtbar. Es ist die billigste Ordnung, die das Veröffentlichen von Daten über ein Flag korrekt macht.

04

Was Sie bauen werden

Das Labor folgt der Lektion: worauf eine Operation warten muss, welche Operationen als Nächstes laufen dürfen, alle Ergebnisse einer Menge von Threads, der Message-Passing-Test, die Prüfung auf veraltete Daten, die Tabelle der vier Ordnungen und die Studie, die einen echten SPY-Preis veröffentlicht.

  • Funktion · FrageBaut auf
  • must_wait(ops, j)Worauf muss diese Operation warten?Einfache Arithmetik
  • eligible(ops, done)Welche Operationen dürfen als Nächstes laufen?Einfache Arithmetik
  • outcomes(programs, memory)Womit können die Threads enden?eligible
  • mp_programs(write_order, read_order)Wie sieht Message Passing aus?Einfache Arithmetik
  • reads_stale(results)Kann der Leser das Flag ohne die Daten sehen?Einfache Arithmetik
  • ordering_table()Welche Ordnungspaare sind sicher?outcomesmp_programs
  • study(bars)Welche Paare schützen einen echten SPY-Preis?outcomesmp_programs

Das Wichtigste

  • Umordnen ist erlaubt, sofern keine Regel es verbietet. Operationen auf derselben Adresse behalten ihre Reihenfolge; Acquire-Loads und Release-Stores fügen die Wände hinzu.
  • Veröffentlichen braucht ein Paar. Release auf dem Flag-Store plus Acquire auf dem Flag-Load: 3 Ergebnisse und kein veralteter Preis; jedes andere Paar hat 4 und kann 0 lesen.
  • Beweisen Sie es über alle Reihenfolgen. Ein Test auf einer Maschine kann zufällig bestehen; der Explorer prüft jede Reihenfolge, die die Regeln erlauben.
Schnelltest
Der Schreiber speichert die Daten und macht dann einen Release-Store des Flags. Der Leser macht einen einfachen (relaxed) Load des Flags, dann einen einfachen Load der Daten. Kann der Leser das Flag gesetzt und die Daten fehlend sehen?
Wähle eine Antwort.

Nächstes Thema: Sie haben jetzt die Werkzeuge, um eine sperrfreie Struktur zu bauen, und ein Vokabular, um zu beschreiben, was sie garantiert. Fortschrittsgarantien benennt die drei Stufen, obstruction-free, lock-free und wait-free, und testet jeden von Ihnen geschriebenen Zähler gegen einen Thread, der im schlimmsten Moment für immer angehalten wird.

Lab: Was bewertet wird

7 Aufgaben · 100 Pkt.
  • Worauf eine Operation wartet15 Pkt.
  • Welche als Nächstes laufen dürfen10 Pkt.
  • Jedes Ergebnis25 Pkt.
  • Der Message-Passing-Test10 Pkt.
  • Veraltete Daten5 Pkt.
  • Die vier Ordnungen10 Pkt.
  • Die Studie mit der SPY-Tabelle25 Pkt.
Explorer · lab-lf-04
▾ lab-lf-04
▾ data
…
bash · im Browser
learner@quantcraft:~/lab-lf-04$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

Alles in lab-lf-04 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