σμ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 AcademyFeuilles de routeParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Feuilles de routeParcours
AO
+1,2 k apprenants
Connexion
  • 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 AcademyFeuilles de routeParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Feuilles de routeParcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Lock-free›Module 4 · Tests et sûreté mémoire›Sujet 4.3·~2 hExpert

Projet final : une diffusion de données de marché sans verrou avec preuves de ses invariants

Position dans le parcoursModule 4 sur 4 · Sujet 3 sur 3
4.3 Projet final : une diffusion de données de marché sans verrou avec preuves de ses invariants
—Score du lab— / 100XP total—Série—

4 min de lecture3 exemples de code

  1. 01Le plan : un journal et un curseur chacun
  2. 02Le consommateur : lire la queue, puis son curseur
  3. 03Trois invariants, puis chaque ordonnancement
  4. 04Ce que vous allez construire

Le projet final est une diffusion : un producteur publie des ticks SPY et plusieurs consommateurs les lisent tous, sans aucun verrou. Tout ce que vous avez construit se retrouve ici. Une queue que seul le producteur déplace, un curseur privé par consommateur pour que personne ne se dispute, un ordre d'écriture qui décide ce qu'un lecteur peut voir, et un explorateur qui transforme « ça a l'air de marcher » en énoncé sur chaque ordonnancement.

01

Le plan : un journal et un curseur chacun

Le flux est un journal qui ne fait que grandir. La position p contient tick<p> et tail dit combien de positions sont publiées. Seul le producteur écrit les ticks et la queue, donc rien ne se dispute côté producteur. Chaque consommateur possède un curseur, la prochaine position qu'il lira, et personne d'autre ne l'écrit. C'est tout le truc d'une diffusion : les consommateurs n'écrivent jamais rien que les autres lisent, donc ajouter un consommateur ne coûte rien aux autres. Le seul fait partagé est la queue, et la seule question est dans quel ordre le producteur écrit le tick et déplace la queue. La déplacer après les données : un lecteur qui voit la nouvelle queue trouve les données. La déplacer avant : un lecteur peut voir une position pas encore écrite.

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

Le consommateur : lire la queue, puis son curseur

Un sondage prend deux tours. Le premier lit la queue. Le second lit le curseur du consommateur et, si le curseur est derrière la queue, lit le tick, avance le curseur et note ce qu'il a obtenu. Rien ne peut changer la position sous ses pieds, car seul ce consommateur écrit son curseur. Ce qui peut mal tourner, c'est le contenu du tick : un lecteur qui arrive après le déplacement de la queue mais avant l'arrivée des données ne lit rien. Le sondage le note None, pour que le vérificateur le voie.

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}
Pourquoi un consommateur ne peut pas simplement attendre

Un sondage qui ne trouve rien renvoie None aussitôt au lieu de tourner jusqu'à l'apparition du tick. Cela garde fini le programme de chaque thread, ce qui permet à l'explorateur de terminer, et c'est aussi le contrat sans verrou : aucun thread ne dépend de la progression d'un autre pour terminer son propre appel.

03

Trois invariants, puis chaque ordonnancement

Que signifie « correct » pour le flux ? Trois énoncés sur l'état final. Déchiré : chaque tick reçu par un consommateur égale le tick mis à cette position (un tick manquant reçu comme None est déchiré). Trou : chaque consommateur a reçu les positions 0, 1, 2, ... sans trou. Dépassement : aucun curseur n'est au-delà de la queue. verify explore chaque ordonnancement d'un producteur et des consommateurs et compte ceux qui brisent chacun. L'explorateur est la preuve : il ne peut pas échantillonner l'exécution chanceuse, il les visite toutes.

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}
Ce que disent les vraies donnéesun producteur avec les deux premières clôtures SPY (9 369 et 9 305 centimes), un consommateur sondant deux fois et un autre une fois
Ordonnancements3,150explorés pour chaque ordre
Données d'abord0ordonnancements brisent un invariant
Queue d'abord869ordonnancements livrent un tick absent
Une exécution calme2ticks reçus par chaque consommateur, dans l'ordre

Le même programme, 3 150 ordonnancements, et une seule inversion de deux lignes fait passer de « aucun brisé » à 869 brisés. La plupart sont des ordonnancements que quelques milliers d'exécutions de test ne rencontreraient presque jamais. C'est l'argument pour explorer au lieu de tester, et c'est toute la compétence en un tableau : un modèle assez petit pour être énuméré, un vérificateur d'invariants et un verdict sur chaque ordonnancement.

04

Ce que vous allez construire

Le labo suit le cours : le flux vide, le producteur dans ses deux ordres, le sondage du consommateur, le vérificateur des trois invariants, le vérificateur qui explore tous les ordonnancements, un rejeu d'une exécution calme où chaque consommateur reçoit les ticks dans l'ordre, et l'étude sur les clôtures SPY.

  • Fonction · questionS'appuie sur
  • make_feed(consumers)Qu'est-ce qui est partagé ?De l'arithmétique simple
  • publish(shared, tick, safe)Données d'abord, ou queue d'abordDe l'arithmétique simple
  • poll(shared, c)Le tick suivant du consommateur cDe l'arithmétique simple
  • violations(final)Quels invariants sont brisés ?De l'arithmétique simple
  • verify(ticks, polls, safe)Le flux est-il correct sous tous les ordonnancements ?explore_allviolations
  • drain(ticks, polls)Que reçoit chaque consommateur ?run_prefix
  • study(bars)Sûr, dangereux et calme sur SPYverifydrain

À retenir

  • La diffusion passe à l'échelle car les lecteurs n'écrivent jamais ce que d'autres lisent. Un curseur privé par consommateur : un nouveau consommateur ne coûte rien aux autres.
  • L'ordre d'écriture est le contrat. Les données avant la queue : sûr ; la queue avant les données brise 869 ordonnancements sur 3 150.
  • Prouvez-le sur chaque ordonnancement. Énoncez les invariants, explorez tout le modèle et lisez le nombre d'ordonnancements brisés.
Vérification rapide
Dans l'ordre dangereux, le producteur déplace la queue d'abord. Quel lecteur voit un tick déchiré ?
Choisissez une réponse.

La compétence sans verrou est terminée. Vous savez construire les pièces (atomiques, compare-and-swap, piles et files), nommer les dangers (courses, ABA, récupération), et surtout vérifier une conception sur chaque ordonnancement au lieu de se fier à une exécution qui a réussi par hasard. L'étape suivante est la même discipline sur du vrai matériel, dans les sujets d'atomiques C++.

Lab : ce qui est noté

7 tâches · 100 pts
  • Le flux vide5 pts
  • Le producteur15 pts
  • Le consommateur20 pts
  • Les invariants15 pts
  • Le vérificateur15 pts
  • Une exécution calme10 pts
  • L'étude sur la table SPY20 pts
Explorateur · lab-lf-11
▾ lab-lf-11
▾ data
…
bash · dans le navigateur
learner@quantcraft:~/lab-lf-11$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-lf-11 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes