σμ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 4 · Tests und Speichersicherheit›Thema 4.3·~2 Std.Experte

Abschlussprojekt: ein sperrfreier Marktdaten-Fan-out mit Beweisen seiner Invarianten

Position in der SkillModul 4 von 4 · Thema 3 von 3
4.3 Abschlussprojekt: ein sperrfreier Marktdaten-Fan-out mit Beweisen seiner Invarianten
—Lab-Punkte— / 100XP gesamt—Serie—

4 Min. Lesezeit3 Codebeispiele

  1. 01Der Entwurf: ein Log und je ein Cursor
  2. 02Der Consumer: Tail lesen, dann den eigenen Cursor
  3. 03Drei Invarianten, dann jeder Schedule
  4. 04Was Sie bauen werden

Das Abschlussprojekt ist ein Fan-out: Ein Producer veröffentlicht SPY-Ticks, und mehrere Consumer lesen alle, ohne jede Sperre. Alles, was Sie gebaut haben, trifft hier zusammen. Ein Tail, den nur der Producer bewegt, ein privater Cursor pro Consumer, damit niemand konkurriert, eine Schreibreihenfolge, die entscheidet, was ein Leser sehen kann, und ein Explorer, der aus „es scheint zu funktionieren“ eine Aussage über jeden Schedule macht.

01

Der Entwurf: ein Log und je ein Cursor

Der Feed ist ein Log, das nur wächst. Position p enthält tick<p>, und tail sagt, wie viele Positionen veröffentlicht sind. Nur der Producer schreibt Ticks und den Tail, auf seiner Seite gibt es also nichts zu bestreiten. Jeder Consumer besitzt einen Cursor, die nächste Position, die er liest, und niemand sonst schreibt ihn. Das ist der ganze Trick eines Fan-outs: Die Consumer schreiben nie etwas, was die anderen lesen, ein weiterer Consumer kostet die anderen also nichts. Die einzige geteilte Tatsache ist der Tail, und die einzige Frage ist, in welcher Reihenfolge der Producer den Tick schreibt und den Tail bewegt. Bewegt er ihn nach den Daten, findet ein Leser, der den neuen Tail sieht, die Daten. Bewegt er ihn davor, kann ein Leser eine noch nicht geschriebene Position sehen.

main.pyPython
def publish(shared, tick, safe=True):    if safe:        yield        pos = shared["tail"]        shared["tick%d" % pos] = tick        shared["put%d" % pos] = tick        yield        shared["tail"] = pos + 1    else:        yield        pos = shared["tail"]        shared["tail"] = pos + 1        yield        shared["tick%d" % pos] = tick        shared["put%d" % pos] = tickfor safe in (True, False):    shared, _ = run_prefix(lambda sh: [publish(sh, 9369, safe)], [0], make_feed(1))    print("safe  " if safe else "unsafe", "after one turn:", {k: v for k, v in shared.items() if k in ("tail", "tick0")})
safe   after one turn: {'tail': 0, 'tick0': 9369}
unsafe after one turn: {'tail': 1}
02

Der Consumer: Tail lesen, dann den eigenen Cursor

Ein Poll braucht zwei Züge. Der erste liest den Tail. Der zweite liest den eigenen Cursor des Consumers und, liegt der Cursor hinter dem Tail, den Tick, rückt den Cursor vor und notiert, was er bekam. An der Position kann sich nichts ändern, denn nur dieser Consumer schreibt seinen Cursor. Schiefgehen kann der Inhalt des Ticks: Ein Leser, der nach der Bewegung des Tails, aber vor dem Eintreffen der Daten kommt, liest nichts. Der Poll notiert das als None, damit der Prüfer es sieht.

main.pyPython
def poll(shared, c):    yield    tail = shared["tail"]    yield    pos = shared["cur%d" % c]    if pos >= tail:        return None    value = shared.get("tick%d" % pos)    shared["cur%d" % c] = pos + 1    shared["got%d_%d" % (c, pos)] = value    return valueready = dict(make_feed(1), tail=1)shared, alive = run_prefix(lambda sh: [poll(sh, 0)], [0, 0], ready)print("tail is 1 but tick0 was never written:", {k: v for k, v in shared.items() if k.startswith(("cur", "got"))})
tail is 1 but tick0 was never written: {'cur0': 1, 'got0_0': None}
Warum ein Consumer nicht einfach warten kann

Ein Poll, der nichts findet, gibt sofort None zurück, statt zu drehen, bis der Tick erscheint. Das hält das Programm jedes Threads endlich, wodurch der Explorer fertig wird, und es ist zugleich der sperrfreie Vertrag: Kein Thread hängt vom Fortschritt eines anderen ab, um seinen eigenen Aufruf abzuschließen.

03

Drei Invarianten, dann jeder Schedule

Was heißt „korrekt“ für den Feed? Drei Aussagen über den Endzustand. Torn: Jeder von einem Consumer empfangene Tick ist gleich dem an dieser Position abgelegten (ein fehlender, als None empfangener Tick ist torn). Gap: Jeder Consumer empfing die Positionen 0, 1, 2, ... ohne Loch. Overrun: Kein Cursor liegt hinter dem Tail. verify erkundet jeden Schedule eines Producers und der Consumer und zählt, welche jede davon brechen. Der Explorer ist der Beweis: Er kann nicht den einen glücklichen Lauf stichprobenartig nehmen, er besucht alle.

main.pyPython
def violations(final):    puts = {int(key[3:]): value for key, value in final.items() if key.startswith("put")}    gots = {}    for key, value in final.items():        if key.startswith("got"):            c, p = key[3:].split("_")            gots[(int(c), int(p))] = value    broken = []    if any(value != puts.get(p) for (c, p), value in gots.items()):        broken.append("torn")    for c in {c for c, _ in gots}:        if sorted(p for cc, p in gots if cc == c) != list(range(sum(1 for cc, _ in gots if cc == c))):            broken.append("gap")            break    if any(key.startswith("cur") and value > final["tail"] for key, value in final.items()):        broken.append("overrun")    return sorted(broken)def verify(ticks, polls, safe=True):    outcomes = explore_all(feed_threads(ticks, polls, safe), make_feed(len(polls)))    broken = {}    delivered = 0    for key, count in outcomes.items():        final = dict(key)        delivered = max(delivered, sum(1 for k in final if k.startswith("got")))        for name in violations(final):            broken[name] = broken.get(name, 0) + count    return {"schedules": sum(outcomes.values()), "broken": dict(sorted(broken.items())), "delivered": delivered}ticks = [9369, 9305]print("safe  ", verify(ticks, (2, 1), True))print("unsafe", verify(ticks, (2, 1), False))
safe   {'schedules': 3150, 'broken': {}, 'delivered': 3}
unsafe {'schedules': 3150, 'broken': {'torn': 869}, 'delivered': 3}
Was die echten Daten sagenein Producer mit den ersten beiden SPY-Schlusskursen (9.369 und 9.305 Cent), ein Consumer pollt zweimal, ein anderer einmal
Schedules3,150je Reihenfolge erkundet
Daten zuerst0Schedules brechen eine Invariante
Tail zuerst869Schedules liefern einen nicht vorhandenen Tick
Ein ruhiger Lauf2Ticks, die jeder Consumer in Reihenfolge empfängt

Dasselbe Programm, 3.150 Schedules, und eine einzige Vertauschung zweier Zeilen macht aus „keiner gebrochen“ 869 gebrochene. Die meisten sind Schedules, die ein paar tausend Testläufe fast nie träfen. Das ist das Argument fürs Erkunden statt Testen und die ganze Fertigkeit in einer Tabelle: ein Modell, klein genug zum Aufzählen, ein Prüfer für die Invarianten und ein Urteil über jeden Schedule.

04

Was Sie bauen werden

Das Labor folgt der Lektion: der leere Feed, der Producer in seinen zwei Reihenfolgen, der Poll des Consumers, der Prüfer der drei Invarianten, der Verifizierer, der alle Schedules erkundet, eine Wiedergabe eines ruhigen Laufs, in dem jeder Consumer die Ticks der Reihe nach empfängt, und die Studie an SPY-Schlusskursen.

  • Funktion · FrageBaut auf
  • make_feed(consumers)Was ist geteilt?Einfache Arithmetik
  • publish(shared, tick, safe)Daten zuerst oder Tail zuerstEinfache Arithmetik
  • poll(shared, c)Der nächste Tick für Consumer cEinfache Arithmetik
  • violations(final)Welche Invarianten brachen?Einfache Arithmetik
  • verify(ticks, polls, safe)Ist der Feed unter allen Schedules korrekt?explore_allviolations
  • drain(ticks, polls)Was empfängt jeder Consumer?run_prefix
  • study(bars)Sicher, unsicher und ruhig an SPYverifydrain

Das Wichtigste

  • Fan-out skaliert, weil Leser nie schreiben, was andere lesen. Ein privater Cursor pro Consumer: Ein neuer Consumer kostet die anderen nichts.
  • Die Schreibreihenfolge ist der Vertrag. Daten vor dem Tail sind sicher; Tail vor den Daten bricht 869 von 3.150 Schedules.
  • Beweisen Sie es über jeden Schedule. Formulieren Sie die Invarianten, erkunden Sie das ganze Modell und lesen Sie die Zahl der gebrochenen Schedules ab.
Schnelltest
In der unsicheren Reihenfolge bewegt der Producer den Tail zuerst. Welcher Leser sieht einen Torn-Tick?
Wähle eine Antwort.

Damit ist die sperrfreie Fertigkeit abgeschlossen. Sie können jetzt die Bausteine bauen (Atomics, Compare-and-swap, Stacks und Queues), die Gefahren benennen (Races, ABA, Freigabe) und vor allem einen Entwurf gegen jeden Schedule prüfen, statt einem zufällig bestandenen Lauf zu trauen. Der nächste Schritt ist dieselbe Disziplin auf echter Hardware, in den C++-Atomics-Themen.

Lab: Was bewertet wird

7 Aufgaben · 100 Pkt.
  • Der leere Feed5 Pkt.
  • Der Producer15 Pkt.
  • Der Consumer20 Pkt.
  • Die Invarianten15 Pkt.
  • Der Verifizierer15 Pkt.
  • Ein ruhiger Lauf10 Pkt.
  • Die Studie mit der SPY-Tabelle20 Pkt.
Explorer · lab-lf-11
▾ lab-lf-11
▾ data
…
bash · im Browser
learner@quantcraft:~/lab-lf-11$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

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