σμ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.3·~1 h 10 minAvanzado

Colas de varios productores y varios consumidores

Posición en la rutaMódulo 3 de 4 · Tema 3 de 3
3.3 Colas de varios productores y varios consumidores
—Puntuación del lab— / 100XP total—Racha—

5 min de lectura4 ejemplos de código

  1. 01Un ticket no es una entrega
  2. 02Comprobar todas las planificaciones
  3. 03Lo que aporta el número de secuencia
  4. 04Lo que va a construir

Con un productor y un consumidor una posición tiene un escritor. Con muchos, las posiciones deben repartirse atómicamente, y un ticket no basta: el productor que tiene la posición 5 aún no ha escrito su elemento. Un número de secuencia en cada casilla dice a quién le toca la casilla. Sobre 2756 planificaciones de dos productores y 1600 de dos consumidores, esta cola nunca se rompe.

01

Un ticket no es una entrega

Una cola acotada de capacity casillas usa dos contadores que solo crecen. Un productor toma la siguiente posición con un comparar e intercambiar sobre enq; un consumidor toma la siguiente con uno sobre deq. La posición p vive en la casilla p % capacity. El comparar e intercambiar hace seguras las reservas: dos productores nunca obtienen la misma posición. Pero reservar una posición no es escribir el elemento. Entre el intercambio y la escritura la casilla está reservada y vacía, y un consumidor que la leyera ahora leería basura. Cada casilla lleva por eso un número de secuencia que solo avanza en el momento correcto.

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}

Lea las tres ejecuciones. Tras [0, 0, 0] el productor ha reservado la posición 0 (enq: 1), guardado data0 y subido seq0 a 1: el elemento está listo. Tras los tres turnos del consumidor, got0: 7 y seq0: 2: la casilla queda libre para la siguiente vuelta. En la última ejecución el consumidor mira entre la reserva del productor y su publicación, ve seq0 = 0 en vez de 1 y se va con las manos vacías. No lee basura. Solo dice «nada listo todavía», y el elemento lo encontrará un intento posterior.

02

Comprobar todas las planificaciones

¿Qué debe prometer ahora la cola? Los productores registran cada elemento bajo su posición (put3), los consumidores registran lo que tomaron bajo la posición que reservaron (got3). Si la cola funciona, cada posición sacada contiene el elemento que se puso allí (sin mismatch), y las posiciones sacadas son 0, 1, 2, ... sin huecos ni repeticiones (sin gap). Eso da a la cola su orden: el orden de las posiciones. violations lee esos dos hechos de un estado final y verify lanza el explorador para cualquier número de productores y consumidores. Aquí, un solo productor con dos elementos contra dos consumidores, y un solo elemento contra un consumidor.

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}
DefiniciónNúmero de secuencia

Un contador en cada casilla que registra a quién le toca. Solo cambia cuando se escribe el elemento (a p + 1) y cuando se toma (a p + capacity), así que un hilo que lo lee sabe si el elemento es seguro de usar sin ningún cerrojo.

03

Lo que aporta el número de secuencia

Para ver su valor, quitémoslo. El consumidor descuidado siguiente reserva una posición con el comparar e intercambiar y luego lee la casilla enseguida, sin mirar seq. Contra un productor con un solo elemento tiene 20 planificaciones, y en 10 de ellas reserva una posición antes de que el productor haya publicado nada y devuelve None como elemento, o el elemento equivocado. La cola real tiene 4 planificaciones para el mismo programa y ninguna se rompe. El número de secuencia es exactamente la comprobación que convierte un ticket reservado en un elemento entregado.

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
La capacidad debe ser al menos dos

Con una sola casilla, el valor p + 1 que significa «lista para la posición p» es también el que significa «libre para la posición p + 1», y un productor podría sobrescribir un elemento no leído. Por eso las implementaciones reales redondean la capacidad a una potencia de dos, al menos 2, lo que además convierte % capacity en una máscara barata.

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}
Qué dicen los datos realeslos dos primeros cierres de SPY (9369 y 9305 centavos) por una cola de dos casillas
Dos productores, un consumidor2,756planificaciones, 0 rompen un invariante
Un productor, dos consumidores1,600planificaciones, 0 rompen un invariante
Consumidor descuidado10de 20 planificaciones leen un elemento ausente

Ambas formas de la cola son correctas sobre todas las planificaciones, con el explorador como testigo, mientras que una versión sin la comprobación de secuencia falla en la mitad de su espacio (mucho menor). Productores y consumidores nunca se esperan: un consumidor que no encuentra nada listo simplemente vuelve, y un productor que encuentra la cola llena devuelve False. Ese es el precio y el regalo de una cola sin cerrojos: las operaciones pueden fallar, pero nunca bloquean.

04

Lo que va a construir

El laboratorio sigue la lección: la cola vacía con sus números de secuencia, el enqueue y el dequeue con reserva por comparar e intercambiar y publicación, la comprobación de los dos invariantes, el verificador que lanza el explorador para cualquier número de productores y consumidores, y el estudio sobre dos cierres de SPY.

  • Función · preguntaSe apoya en
  • init_queue(capacity)¿Cómo es una cola vacía?Aritmética simple
  • enqueue(shared, item, capacity)¿Cómo reserva y publica un productor?Aritmética simple
  • dequeue(shared, capacity)¿Cómo reserva y libera un consumidor?Aritmética simple
  • violations(final)¿Qué invariantes rompe un estado final?Aritmética simple
  • verify(producers, consumers, capacity)¿Es correcta la cola bajo todas las planificaciones?explore_allviolations
  • study(bars)¿Resisten ambas formas con cierres de SPY?verify

Ideas clave

  • Tickets con comparar e intercambiar, disponibilidad con números de secuencia. Reservar una posición la hace suya; el número de secuencia dice a todos cuándo está listo el elemento que contiene.
  • No listo no es un error. Un consumidor que no encuentra nada listo devuelve None y lo intenta más tarde; nunca espera y nunca lee basura.
  • Demuéstrelo, quitando el número de secuencia como control. 1600 y 2756 planificaciones sin violación; el consumidor descuidado se rompe en 10 de 20.
Comprobación rápida
Una cola tiene 4 casillas. Un consumidor reservó la posición 6 y liberó su casilla. ¿Cuál es ahora el número de secuencia de esa casilla y para qué posición está libre?
Elige una respuesta.

Siguiente tema: ya ha demostrado la corrección de programas pequeños ejecutando todas las planificaciones. Probar código concurrente lo compara con las herramientas que usan los equipos reales: un detector de carreras que observa una ejecución y señala los accesos que podrían competir, y un verificador de modelos que hace lo que hace su explorador, pero con los trucos que le permiten escalar.

Lab: qué se califica

6 tareas · 100 pts
  • La cola vacía5 pts
  • Enqueue25 pts
  • Dequeue25 pts
  • Los invariantes10 pts
  • Verificar sobre todas las planificaciones15 pts
  • El estudio con la tabla de SPY20 pts
Explorador · lab-lf-08
▾ lab-lf-08
▾ data
…
bash · en el navegador
learner@quantcraft:~/lab-lf-08$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

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