σμ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 3 · Colas sin cerrojos›Tema 3.1·~1 h 30 minAvanzado

Un búfer circular de un productor y un consumidor

Posición en la rutaMódulo 3 de 4 · Tema 1 de 3
3.1 Un búfer circular de un productor y un consumidor
—Puntuación del lab— / 100XP total—Racha—

5 min de lectura4 ejemplos de código

  1. 01Un círculo con un solo escritor por posición
  2. 02Comprobar los invariantes sobre todas las planificaciones
  3. 03Qué lo rompe: un segundo productor
  4. 04Lo que va a construir

La cola sin cerrojos más simple no necesita comparar e intercambiar en absoluto. Dé a cada posición un solo escritor: solo el productor escribe la cola, solo el consumidor escribe la cabeza. Una lectura obsoleta de la posición del otro lado es siempre segura, así que un búfer circular puede pasar ticks entre dos hilos sin cerrojo. El explorador lo demuestra sobre 291 planificaciones, y lo muestra fallando en 458 de 1072 cuando se une un segundo productor.

01

Un círculo con un solo escritor por posición

Un búfer circular mantiene dos contadores que solo crecen. El productor escribe la casilla tail % capacity y luego publica subiendo tail; el consumidor lee la casilla head % capacity y luego la libera subiendo head. El búfer está lleno cuando tail - head == capacity y vacío cuando head == tail. ¿Por qué es seguro sin cerrojo? El productor es el único escritor de tail, así que puede guardar su propia copia local. Lee head para buscar espacio y, si ese valor está obsoleto, la cabeza obsoleta es menor que la real, así que el productor cree el búfer más lleno de lo que está y, como mucho, descarta un elemento, sin sobrescribir nunca una casilla sin leer. La tail obsoleta del consumidor también es menor, así que como mucho ve el búfer vacío. Es el principio de escritor único.

main.pyPython
def initial_state():    return {"head": 0, "tail": 0, "pushed": (), "popped": ()}def ring_producer(items, capacity):    def thread(shared):        tail = 0        for item in items:            yield            head = shared.get("head", 0)            if tail - head < capacity:                yield                shared["slot%d" % (tail % capacity)] = item                yield                tail += 1                shared["tail"] = tail                shared["pushed"] = shared.get("pushed", ()) + (item,)    return threaddef ring_consumer(attempts, capacity):    def thread(shared):        head = 0        for _ in range(attempts):            yield            tail = shared.get("tail", 0)            if head != tail:                yield                item = shared.get("slot%d" % (head % capacity))                yield                head += 1                shared["head"] = head                shared["popped"] = shared.get("popped", ()) + (item,)    return threadtwo = lambda shared: [ring_producer((7, 8), 2)(shared), ring_consumer(2, 2)(shared)]for prefix in ([0, 0, 0], [0, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]):    print(prefix, run_prefix(two, prefix, initial_state())[0])
[0, 0, 0] {'head': 0, 'tail': 1, 'pushed': (7,), 'popped': (), 'slot0': 7}
[0, 0, 0, 1, 1, 1] {'head': 1, 'tail': 1, 'pushed': (7,), 'popped': (7,), 'slot0': 7}
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1] {'head': 1, 'tail': 2, 'pushed': (7, 8), 'popped': (7,), 'slot0': 7, 'slot1': 8}

Tres turnos del productor empujan el primer tick: lee la cabeza, guarda el elemento en slot0 y publica tail: 1. Tras tres turnos del consumidor, head: 1 y popped: (7,). En la última línea el productor ha empujado ambos elementos (slot0: 7, slot1: 8) y el consumidor ha tomado el primero; el hilo 1 sigue vivo porque le queda un intento.

02

Comprobar los invariantes sobre todas las planificaciones

¿Qué debe prometer una cola? Dos cosas. Los elementos sacados son los elementos metidos, en el mismo orden y sin huecos: popped es un prefijo de pushed (la propiedad FIFO). Y no se saca nada que nunca se metió: len(popped) <= len(pushed). Los hilos registran lo que meten y sacan en el mismo turno que termina la operación, así que violations puede comprobar ambas en cualquier estado final. verify ejecuta entonces todo el programa bajo cada planificación con explore_all y cuenta las planificaciones que rompen cada invariante. Un diccionario broken vacío sobre todas las planificaciones es una demostración, para ese tamaño de programa.

main.pyPython
def violations(final):    pushed, popped = tuple(final.get("pushed", ())), tuple(final.get("popped", ()))    broken = []    if popped != pushed[: len(popped)]:        broken.append("order")    if len(popped) > len(pushed):        broken.append("count")    return sorted(broken)def verify(producers, attempts, capacity):    def make_threads(shared):        return [ring_producer(items, capacity)(shared) for items in producers] + [ring_consumer(attempts, capacity)(shared)]    outcomes = explore_all(make_threads, initial_state())    broken = {}    pushed = popped = 0    for key, count in outcomes.items():        final = dict(key)        pushed = max(pushed, len(final.get("pushed", ())))        popped = max(popped, len(final.get("popped", ())))        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(violations({"pushed": (7, 8), "popped": (8,)}))print(verify([(7, 8)], 2, 2))print(verify([(7, 8)], 2, 1))
['order']
{'schedules': 101, 'broken': {}, 'pushed': 2, 'popped': 2}
{'schedules': 25, 'broken': {}, 'pushed': 2, 'popped': 2}

Un productor con dos elementos en un búfer de dos casillas, leído por un consumidor con dos intentos, tiene 101 planificaciones y ninguna rompe un invariante. Con un búfer de una sola casilla el productor tiene que descartar un elemento cuando el consumidor es lento, pero sigue siendo correcto: 25 planificaciones, ninguna violación. La lista sacada (8,) tras empujar (7, 8) es, como se espera, una violación del orden.

DefiniciónPrincipio de escritor único

Cada variable compartida tiene exactamente un hilo que la escribe, y cualquier número que solo la lee. Con un solo escritor no hay carrera sobre esa variable, y un lector que ve un valor antiguo sigue seguro si el valor antiguo solo se equivoca del lado seguro.

03

Qué lo rompe: un segundo productor

El argumento de seguridad se apoya en un hecho: tail tiene un escritor. Añada un segundo productor y dos hilos mantienen cada uno su idea de la cola. Ambos leen el mismo head, calculan la misma casilla y la segunda escritura sobrescribe la primera. El explorador lo encuentra: 458 de las 1072 planificaciones de dos productores (un tick cada uno) terminan con un popped que no es prefijo de pushed. La solución no es un parche de este búfer sino otra estructura, una cola de varios productores, que los temas siguientes construyen con comparar e intercambiar.

main.pyPython
print(verify([(7,), (8,)], 2, 2))
{'schedules': 1072, 'broken': {'order': 458}, 'pushed': 2, 'popped': 1}
En hardware real el orden de las dos escrituras importa

Esta máquina ejecuta cada operación en orden. En un procesador real el productor debe publicar tail con una escritura release y el consumidor leerla con una carga acquire, o el consumidor puede ver la cola nueva antes que el elemento (los datos obsoletos del tema anterior). Ponga también head y tail en líneas de caché distintas, o los dos hilos se disputan una línea (falso compartir).

main.pyPython
ticks = [int(round(float(c) * 100)) for c in load_bars()["close"].iloc[:4]]print(ticks)print("one producer: ", verify([ticks[:2]], 3, 2))print("two producers:", verify([ticks[:1], ticks[1:2]], 2, 2))
[9369, 9305, 9354, 9349]
one producer:  {'schedules': 291, 'broken': {}, 'pushed': 2, 'popped': 2}
two producers: {'schedules': 1072, 'broken': {'order': 458}, 'pushed': 2, 'popped': 1}
Qué dicen los datos realeslos primeros cuatro cierres de SPY (9369, 9305, 9354 y 9349 centavos) empujados por un búfer
Un productor291planificaciones, 0 rompen un invariante
Dos productores1,072planificaciones exploradas
Orden roto458de las planificaciones de dos productores
Proporción rota43%de las planificaciones de dos productores
Planificaciones según el veredicto
Un productor: todas correctas291
Dos productores: correctas614
Dos productores: orden roto458

Toda la lección cabe en dos números: un escritor por posición basta para la corrección en 291 planificaciones, y dos escritores en la misma posición fallan en el 43 % de 1072. El explorador no adivinó; ejecutó todos los intercalados.

04

Lo que va a construir

El laboratorio sigue la lección: el búfer vacío, los hilos productor y consumidor, la comprobación de los dos invariantes, el verificador que lanza el explorador y el estudio que envía cierres de SPY por el búfer con uno y con dos productores.

  • Función · preguntaSe apoya en
  • initial_state()¿Cómo es un búfer vacío?Aritmética simple
  • ring_producer(items, capacity)¿Cómo empuja el productor?Aritmética simple
  • ring_consumer(attempts, capacity)¿Cómo saca el consumidor?Aritmética simple
  • violations(final)¿Qué invariantes rompe este estado?Aritmética simple
  • verify(producers, attempts, capacity)¿Es correcto el búfer bajo todas las planificaciones?explore_allviolations
  • study(bars)¿Qué se rompe con un segundo productor?verify

Ideas clave

  • Un escritor por posición. Solo el productor escribe la cola y solo el consumidor la cabeza, así que no hace falta comparar e intercambiar.
  • Una lectura obsoleta se equivoca del lado seguro. Una cabeza obsoleta hace parecer el búfer más lleno y una cola obsoleta más vacío: como mucho se descarta un elemento o una extracción no encuentra nada.
  • Rompa el contrato y el explorador lo encuentra. Dos productores sobre una sola cola rompen el orden en 458 de 1072 planificaciones.
Comprobación rápida
Un búfer tiene 4 casillas. El productor ha empujado 9 elementos en total y el consumidor ha sacado 6. ¿Cuántos elementos hay en el búfer y en qué casilla se guardará el siguiente?
Elige una respuesta.

Siguiente tema: el truco del escritor único tiene un límite, y comparar e intercambiar también. El problema ABA y los punteros etiquetados construye una pila sin cerrojos sobre comparar e intercambiar, muestra una planificación en la que el intercambio tiene éxito aunque la pila haya cambiado por debajo, y lo repara con una etiqueta de versión.

Lab: qué se califica

6 tareas · 100 pts
  • El búfer vacío5 pts
  • El productor20 pts
  • El consumidor20 pts
  • Los invariantes10 pts
  • Verificar sobre todas las planificaciones15 pts
  • El estudio con la tabla de SPY30 pts
Explorador · lab-lf-06
▾ lab-lf-06
▾ data
…
bash · en el navegador
learner@quantcraft:~/lab-lf-06$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-lf-06 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