σμ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 AcademyHojas de rutaRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Hojas de rutaRutas
AO
+1,2 k estudiantes
Iniciar sesión
  • 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 AcademyHojas de rutaRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Hojas de rutaRutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Ruta de Lock-free›Módulo 4 · Pruebas y seguridad de memoria›Tema 4.3·~2 hExperto

Proyecto final: una difusión de datos de mercado sin cerrojos con pruebas de sus invariantes

Posición en la rutaMódulo 4 de 4 · Tema 3 de 3
4.3 Proyecto final: una difusión de datos de mercado sin cerrojos con pruebas de sus invariantes
—Puntuación del lab— / 100XP total—Racha—

4 min de lectura3 ejemplos de código

  1. 01El diseño: un registro y un cursor cada uno
  2. 02El consumidor: leer la cola y luego su cursor
  3. 03Tres invariantes y luego cada planificación
  4. 04Lo que va a construir

El proyecto final es una difusión: un productor publica ticks de SPY y varios consumidores los leen todos, sin ningún cerrojo. Todo lo que ha construido se junta aquí. Una cola que solo mueve el productor, un cursor privado por consumidor para que nadie compita, un orden de escritura que decide lo que puede ver un lector y un explorador que convierte «parece funcionar» en una afirmación sobre cada planificación.

01

El diseño: un registro y un cursor cada uno

El flujo es un registro que solo crece. La posición p contiene tick<p> y tail dice cuántas posiciones están publicadas. Solo el productor escribe ticks y la cola, así que no hay nada que disputar en su lado. Cada consumidor posee un cursor, la siguiente posición que leerá, y nadie más lo escribe. Ese es todo el truco de una difusión: los consumidores nunca escriben nada que los otros lean, así que añadir un consumidor no cuesta nada a los demás. El único hecho compartido es la cola, y la única pregunta es en qué orden escribe el productor el tick y mueve la cola. Si la mueve después de los datos, un lector que ve la nueva cola encuentra los datos. Si la mueve antes, un lector puede ver una posición aún no escrita.

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

El consumidor: leer la cola y luego su cursor

Un sondeo toma dos turnos. El primero lee la cola. El segundo lee el cursor del consumidor y, si el cursor está por detrás de la cola, lee el tick, avanza el cursor y anota lo que obtuvo. Nada puede cambiar la posición bajo sus pies, porque solo este consumidor escribe su cursor. Lo que puede fallar es el contenido del tick: un lector que entra tras mover la cola pero antes de que lleguen los datos no lee nada. El sondeo lo anota como None, para que el comprobador lo vea.

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}
Por qué un consumidor no puede simplemente esperar

Un sondeo que no encuentra nada devuelve None de inmediato en lugar de girar hasta que aparezca el tick. Eso mantiene finito el programa de cada hilo, lo que permite terminar al explorador, y es también el contrato sin cerrojos: ningún hilo depende de que otro progrese para completar su propia llamada.

03

Tres invariantes y luego cada planificación

¿Qué significa «correcto» para el flujo? Tres afirmaciones sobre el estado final. Roto: cada tick recibido por un consumidor es igual al tick puesto en esa posición (un tick ausente recibido como None está roto). Hueco: cada consumidor recibió las posiciones 0, 1, 2, ... sin huecos. Desbordamiento: ningún cursor está más allá de la cola. verify explora cada planificación de un productor y los consumidores y cuenta las que rompen cada una. El explorador es la prueba: no puede muestrear la ejecución afortunada, las visita todas.

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}
Qué dicen los datos realesun productor con los dos primeros cierres de SPY (9369 y 9305 centavos), un consumidor que sondea dos veces y otro una
Planificaciones3,150exploradas para cada orden
Datos primero0planificaciones rompen un invariante
Cola primero869planificaciones entregan un tick que no está
Una ejecución tranquila2ticks recibidos por cada consumidor, en orden

El mismo programa, 3150 planificaciones, y un solo cambio de orden de dos líneas convierte «ninguna rota» en 869 rotas. La mayoría son planificaciones que unos miles de ejecuciones de prueba casi nunca alcanzarían. Ese es el argumento para explorar en vez de probar, y es toda la habilidad en una tabla: un modelo lo bastante pequeño para enumerarlo, un comprobador de invariantes y un veredicto sobre cada planificación.

04

Lo que va a construir

El laboratorio sigue la lección: el flujo vacío, el productor en sus dos órdenes, el sondeo del consumidor, el comprobador de los tres invariantes, el verificador que explora todas las planificaciones, una repetición de una ejecución tranquila en la que cada consumidor recibe los ticks en orden y el estudio sobre cierres de SPY.

  • Función · preguntaSe apoya en
  • make_feed(consumers)¿Qué se comparte?Aritmética simple
  • publish(shared, tick, safe)Datos primero o cola primeroAritmética simple
  • poll(shared, c)El siguiente tick del consumidor cAritmética simple
  • violations(final)¿Qué invariantes se rompieron?Aritmética simple
  • verify(ticks, polls, safe)¿Es correcto el flujo bajo todas las planificaciones?explore_allviolations
  • drain(ticks, polls)¿Qué recibe cada consumidor?run_prefix
  • study(bars)Seguro, inseguro y tranquilo en SPYverifydrain

Ideas clave

  • La difusión escala porque los lectores nunca escriben lo que otros leen. Un cursor privado por consumidor: un consumidor nuevo no cuesta nada a los demás.
  • El orden de escritura es el contrato. Los datos antes de la cola son seguros; la cola antes que los datos rompe 869 de 3150 planificaciones.
  • Demuéstrelo sobre cada planificación. Enuncie los invariantes, explore todo el modelo y lea el recuento de planificaciones rotas.
Comprobación rápida
En el orden inseguro el productor mueve la cola primero. ¿Qué lector ve un tick roto?
Elige una respuesta.

Con esto termina la habilidad sin cerrojos. Ya sabe construir las piezas (atómicos, compare-and-swap, pilas y colas), nombrar los peligros (carreras, ABA, recuperación) y, sobre todo, comprobar un diseño contra cada planificación en lugar de fiarse de una ejecución que pasó por casualidad. El siguiente paso es la misma disciplina en hardware real, en los temas de atómicos de C++.

Lab: qué se califica

7 tareas · 100 pts
  • El flujo vacío5 pts
  • El productor15 pts
  • El consumidor20 pts
  • Los invariantes15 pts
  • El verificador15 pts
  • Una ejecución tranquila10 pts
  • El estudio con la tabla de SPY20 pts
Explorador · lab-lf-11
▾ lab-lf-11
▾ data
…
bash · en el navegador
learner@quantcraft:~/lab-lf-11$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-lf-11 vuelve a los archivos iniciales y se limpia el terminal. Tu solución actual se perderá: no se puede deshacer.

Se restablecerá
  • main.py → código inicial
  • Historial del terminal y salida de pruebas
  • Cambios sin enviar
Se conservará
  • Mejor puntuación del lab
  • Tu XP y tu racha
  • Progreso de la lección y notas