σμ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 3 · Sperrfreie Warteschlangen›Thema 3.3·~1 Std. 10 Min.Fortgeschritten

Warteschlangen mit mehreren Producern und Consumern

Position in der SkillModul 3 von 4 · Thema 3 von 3
3.3 Warteschlangen mit mehreren Producern und Consumern
—Lab-Punkte— / 100XP gesamt—Serie—

5 Min. Lesezeit4 Codebeispiele

  1. 01Ein Ticket ist keine Lieferung
  2. 02Alle Schedules prüfen
  3. 03Was die Sequenznummer bringt
  4. 04Was Sie bauen werden

Mit einem Producer und einem Consumer hat eine Position einen Schreiber. Mit vielen müssen die Positionen atomar vergeben werden, und ein Ticket genügt nicht: Der Producer, der Position 5 hält, hat sein Element noch nicht geschrieben. Eine Sequenznummer an jeder Zelle sagt, wer an der Reihe ist. Über 2.756 Schedules zweier Producer und 1.600 zweier Consumer bricht diese Warteschlange nie.

01

Ein Ticket ist keine Lieferung

Eine beschränkte Warteschlange mit capacity Zellen nutzt zwei Zähler, die nur wachsen. Ein Producer nimmt die nächste Position mit einem Compare-and-Swap auf enq; ein Consumer die nächste mit einem auf deq. Position p liegt in Zelle p % capacity. Das Compare-and-Swap macht die Vergabe sicher: Zwei Producer bekommen nie dieselbe Position. Aber eine Position zu belegen heißt nicht, das Element zu schreiben. Zwischen Tausch und Speichern ist die Zelle belegt und leer, und ein Consumer, der sie jetzt läse, läse Müll. Jede Zelle trägt deshalb eine Sequenznummer, die nur im richtigen Moment weitergeschaltet wird.

main.pyPython
def init_queue(capacity):    shared = {"enq": 0, "deq": 0}    for cell in range(capacity):        shared["seq%d" % cell] = cell    return shareddef enqueue(shared, item, capacity):    while True:        yield        pos = shared["enq"]        cell = pos % capacity        diff = shared["seq%d" % cell] - pos        if diff < 0:            return False        if diff > 0:            continue        yield        if compare_and_swap(shared, "enq", pos, pos + 1):            break    yield    shared["data%d" % cell] = item    shared["seq%d" % cell] = pos + 1    shared["put%d" % pos] = item    return Truedef dequeue(shared, capacity):    while True:        yield        pos = shared["deq"]        cell = pos % capacity        diff = shared["seq%d" % cell] - (pos + 1)        if diff < 0:            return None        if diff > 0:            continue        yield        if compare_and_swap(shared, "deq", pos, pos + 1):            break    yield    item = shared.get("data%d" % cell)    shared["seq%d" % cell] = pos + capacity    shared["got%d" % pos] = item    return itemsolo = lambda shared: [producer_thread((7,), 2)(shared), consumer_thread(1, 2)(shared)]for prefix in ([0, 0, 0], [0, 0, 0, 1, 1, 1], [0, 0, 1, 1, 0, 1, 1]):    print(prefix, run_prefix(solo, prefix, init_queue(2))[0])
[0, 0, 0] {'enq': 1, 'deq': 0, 'seq0': 1, 'seq1': 1, 'data0': 7, 'put0': 7}
[0, 0, 0, 1, 1, 1] {'enq': 1, 'deq': 1, 'seq0': 2, 'seq1': 1, 'data0': 7, 'put0': 7, 'got0': 7}
[0, 0, 1, 1, 0, 1, 1] {'enq': 1, 'deq': 0, 'seq0': 1, 'seq1': 1, 'data0': 7, 'put0': 7}

Lesen Sie die drei Läufe. Nach [0, 0, 0] hat der Producer Position 0 belegt (enq: 1), data0 gespeichert und seq0 auf 1 erhöht: Das Element ist bereit. Nach den drei Zügen des Consumers ist got0: 7 und seq0: 2: Die Zelle ist für die nächste Runde frei. Im letzten Lauf schaut der Consumer zwischen Belegung und Veröffentlichung des Producers nach, sieht seq0 = 0 statt 1 und geht mit leeren Händen. Er liest keinen Müll. Er sagt nur „noch nichts bereit“, und ein späterer Versuch findet das Element.

02

Alle Schedules prüfen

Was muss die Warteschlange jetzt versprechen? Die Producer protokollieren jedes Element unter seiner Position (put3), die Consumer protokollieren, was sie nahmen, unter der Position, die sie belegten (got3). Funktioniert die Warteschlange, enthält jede entnommene Position das dort eingefügte Element (kein mismatch), und die entnommenen Positionen sind 0, 1, 2, ... ohne Lücken oder Wiederholungen (kein gap). Das gibt der Warteschlange ihre Ordnung: die Ordnung der Positionen. violations liest diese beiden Tatsachen aus einem Endzustand, und verify startet den Explorer für beliebig viele Producer und Consumer. Hier ein einzelner Producer mit zwei Elementen gegen zwei Consumer und ein einzelnes Element gegen einen Consumer.

main.pyPython
def violations(final):    puts = {int(key[3:]): value for key, value in final.items() if key.startswith("put")}    gots = {int(key[3:]): value for key, value in final.items() if key.startswith("got")}    broken = []    if any(gots[p] != puts.get(p) for p in gots):        broken.append("mismatch")    if sorted(gots) != list(range(len(gots))):        broken.append("gap")    return sorted(broken)def verify(producers, consumers, capacity):    def make_threads(shared):        return [producer_thread(items, capacity)(shared) for items in producers] + [consumer_thread(n, capacity)(shared) for n in consumers]    outcomes = explore_all(make_threads, init_queue(capacity))    broken = {}    pushed = popped = 0    for key, count in outcomes.items():        final = dict(key)        pushed = max(pushed, sum(1 for k in final if k.startswith("put")))        popped = max(popped, 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())), "pushed": pushed, "popped": popped}print(verify([(7,)], [1], 2))print(verify([(7, 8)], [1, 1], 2))
{'schedules': 4, 'broken': {}, 'pushed': 1, 'popped': 1}
{'schedules': 1600, 'broken': {}, 'pushed': 2, 'popped': 2}
DefinitionSequenznummer

Ein Zähler an jeder Zelle, der festhält, wer an der Reihe ist. Er ändert sich nur, wenn das Element geschrieben (auf p + 1) und wenn es genommen wird (auf p + capacity), sodass ein Thread, der ihn liest, ohne jede Sperre weiß, ob das Element sicher nutzbar ist.

03

Was die Sequenznummer bringt

Um den Wert zu sehen, nehmen wir sie weg. Der nachlässige Consumer unten belegt eine Position mit dem Compare-and-Swap und liest die Zelle dann sofort, ohne auf seq zu schauen. Gegen einen Producer mit einem einzigen Element hat er 20 Schedules, und in 10 davon belegt er eine Position, bevor der Producer etwas veröffentlicht hat, und liefert None als Element oder das falsche. Die echte Warteschlange hat für dasselbe Programm 4 Schedules, und keiner bricht. Die Sequenznummer ist genau die Prüfung, die ein belegtes Ticket in ein geliefertes Element verwandelt.

main.pyPython
def careless_dequeue(shared, capacity):    yield    pos = shared["deq"]    cell = pos % capacity    yield    if not compare_and_swap(shared, "deq", pos, pos + 1):        return None    yield    item = shared.get("data%d" % cell)    shared["got%d" % pos] = item    return itemdef careless_consumer(attempts, capacity):    def thread(shared):        for _ in range(attempts):            yield from careless_dequeue(shared, capacity)    return threadmake = lambda shared: [producer_thread((7,), 2)(shared), careless_consumer(1, 2)(shared)]outcomes = explore_all(make, init_queue(2))broken = sum(n for key, n in outcomes.items() if violations(dict(key)))print(sum(outcomes.values()), "schedules,", broken, "break the invariants")
20 schedules, 10 break the invariants
Die Kapazität muss mindestens zwei sein

Bei nur einer Zelle ist der Wert p + 1, der „bereit für Position p“ bedeutet, auch der, der „frei für Position p + 1“ bedeutet, und ein Producer könnte ein ungelesenes Element überschreiben. Darum runden echte Implementierungen die Kapazität auf eine Zweierpotenz auf, mindestens 2, was % capacity auch zu einer billigen Maske macht.

main.pyPython
ticks = [int(round(float(c) * 100)) for c in load_bars()["close"].iloc[:2]]print(ticks)print("two producers:", verify([(ticks[0],), (ticks[1],)], [2], 2))print("two consumers:", verify([tuple(ticks)], [1, 1], 2))
[9369, 9305]
two producers: {'schedules': 2756, 'broken': {}, 'pushed': 2, 'popped': 2}
two consumers: {'schedules': 1600, 'broken': {}, 'pushed': 2, 'popped': 2}
Was die echten Daten sagendie ersten zwei SPY-Schlusskurse (9.369 und 9.305 Cent) durch eine Warteschlange mit zwei Zellen
Zwei Producer, ein Consumer2,756Schedules, 0 brechen eine Invariante
Ein Producer, zwei Consumer1,600Schedules, 0 brechen eine Invariante
Nachlässiger Consumer10von 20 Schedules lesen ein fehlendes Element

Beide Formen der Warteschlange sind über alle Schedules korrekt, der Explorer ist der Zeuge, während eine Version ohne Sequenzprüfung in der Hälfte ihres (viel kleineren) Raums scheitert. Producer und Consumer warten nie aufeinander: Ein Consumer, der nichts Bereites findet, kehrt einfach zurück, und ein Producer, der die Warteschlange voll findet, gibt False zurück. Das ist Preis und Geschenk einer sperrfreien Warteschlange: Operationen können scheitern, blockieren aber nie.

04

Was Sie bauen werden

Das Labor folgt der Lektion: die leere Warteschlange mit ihren Sequenznummern, Enqueue und Dequeue mit Compare-and-Swap-Belegung und Veröffentlichung, die Prüfung der zwei Invarianten, der Verifizierer, der den Explorer für beliebig viele Producer und Consumer startet, und die Studie an zwei SPY-Schlusskursen.

  • Funktion · FrageBaut auf
  • init_queue(capacity)Wie sieht eine leere Warteschlange aus?Einfache Arithmetik
  • enqueue(shared, item, capacity)Wie belegt und veröffentlicht ein Producer?Einfache Arithmetik
  • dequeue(shared, capacity)Wie belegt und gibt ein Consumer frei?Einfache Arithmetik
  • violations(final)Welche Invarianten bricht ein Endzustand?Einfache Arithmetik
  • verify(producers, consumers, capacity)Stimmt die Warteschlange unter jedem Schedule?explore_allviolations
  • study(bars)Halten beide Formen bei SPY-Schlusskursen?verify

Das Wichtigste

  • Tickets per Compare-and-Swap, Bereitschaft per Sequenznummern. Eine Position zu belegen macht sie zu Ihrer; die Sequenznummer sagt allen, wann das Element darin bereit ist.
  • Nicht bereit ist kein Fehler. Ein Consumer, der nichts Bereites findet, gibt None zurück und versucht es später; er wartet nie und liest nie Müll.
  • Beweisen Sie es, mit entfernter Sequenznummer als Kontrolle. 1.600 und 2.756 Schedules ohne Verletzung; der nachlässige Consumer bricht in 10 von 20.
Schnelltest
Eine Warteschlange hat 4 Zellen. Ein Consumer hat Position 6 belegt und ihre Zelle freigegeben. Welche Sequenznummer hat diese Zelle jetzt, und für welche Position ist sie frei?
Wähle eine Antwort.

Nächstes Thema: Sie haben nun die Korrektheit kleiner Programme bewiesen, indem Sie alle Schedules ausgeführt haben. Nebenläufigen Code testen vergleicht das mit den Werkzeugen echter Teams: einem Race-Detektor, der einen Lauf beobachtet und Zugriffe markiert, die wettlaufen könnten, und einem Model Checker, der tut, was Ihr Explorer tut, aber mit den Tricks, die ihn skalieren lassen.

Lab: Was bewertet wird

6 Aufgaben · 100 Pkt.
  • Die leere Warteschlange5 Pkt.
  • Enqueue25 Pkt.
  • Dequeue25 Pkt.
  • Die Invarianten10 Pkt.
  • Über alle Schedules verifizieren15 Pkt.
  • Die Studie mit der SPY-Tabelle20 Pkt.
Explorer · lab-lf-08
▾ lab-lf-08
▾ data
…
bash · im Browser
learner@quantcraft:~/lab-lf-08$
Python noch nicht geladenPythonLn 1, Col 1Spaces: 4UTF-8

Dieses Thema zurücksetzen?

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