σμ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 2 · Atómicas, orden y garantías›Tema 2.3·~45 minAvanzado

Garantías de progreso: de obstruction-free a wait-free

Posición en la rutaMódulo 2 de 4 · Tema 3 de 3
2.3 Garantías de progreso: de obstruction-free a wait-free
—Puntuación del lab— / 100XP total—Racha—

5 min de lectura4 ejemplos de código

  1. 01Un hilo en pausa
  2. 02Sin cerrojos: alguien siempre termina
  3. 03Wait-free: todos terminan
  4. 04Lo que va a construir

Tres contadores dan la respuesta correcta bajo todas las planificaciones y aun así no son iguales. Pause un hilo en el peor momento: el cerrojo de espera activa detiene a todos, el contador con comparar e intercambiar deja pasar a alguien y el contador fetch-and-add deja que cada hilo termine en 1 turno. Son los tres peldaños de la escala de garantías de progreso.

01

Un hilo en pausa

Un sistema operativo real puede sacar un hilo de su núcleo en cualquier momento: termina su porción de tiempo, un fallo de página lo manda al disco, llega una interrupción. Una buena estructura concurrente debe sobrevivir a eso. Precisamos la pregunta con un adversario. Ejecuta un prefix de turnos a su elección, luego pausa un hilo para siempre y damos un budget de rondas a todos los demás. Si algún hilo no pausado aún no ha terminado, la estructura puede ser detenida. Un cerrojo de espera activa falla esta prueba de la forma más simple: si el adversario pausa el hilo que tiene el cerrojo, los demás giran hasta que acaba el presupuesto.

main.pyPython
def locked_increments(k):    def thread(shared):        for _ in range(k):            while True:                yield                if not shared.get("lock", 0):                    shared["lock"] = 1                    break            yield            value = shared.get("x", 0)            yield            shared["x"] = value + 1            yield            shared["lock"] = 0    return threaddef progress_without(make_threads, prefix, paused, budget=50):    n = len(make_threads({}))    allowed = [tid for tid in range(n) if tid not in paused]    _, alive = run_prefix(make_threads, list(prefix) + allowed * budget)    return [tid for tid in allowed if tid not in alive]lk = lambda shared: [locked_increments(1)(shared), locked_increments(1)(shared)]print(run_prefix(lk, [0]))print("thread 0 paused holding the lock:", progress_without(lk, [0], [0]))print("thread 0 finished first:         ", progress_without(lk, [0, 0, 0, 0], [0]))
({'lock': 1}, [0, 1])
thread 0 paused holding the lock: []
thread 0 finished first:          [1]

Tras un turno el hilo 0 tiene el cerrojo ('lock': 1). Pausado ahí, el hilo 1 no termina nada en cincuenta rondas: []. Si deja que el hilo 0 haga primero sus cuatro turnos, el cerrojo vuelve a estar libre y el hilo 1 termina: [1]. La diferencia es solo dónde golpea el adversario. Un cerrojo convierte cualquier pausa dentro de la sección crítica en una pausa del programa entero.

02

Sin cerrojos: alguien siempre termina

is_blocking automatiza al adversario. Prueba todos los prefijos de unos pocos turnos, pausa cada hilo vivo por turno y pregunta a progress_without si los demás aún terminan. Para el cerrojo de espera activa la respuesta es sí tras un solo turno. Para el contador con comparar e intercambiar nunca lo es: un hilo pausado entre su lectura y su intercambio simplemente deja el valor sin cambiar, así que el intercambio del otro hilo tiene éxito. Esta es la definición de sin cerrojos: el sistema en su conjunto siempre avanza, aunque un hilo esté detenido para siempre.

main.pyPython
def is_blocking(make_threads, depth=3, budget=50):    def visit(prefix):        _, alive = run_prefix(make_threads, prefix)        for paused in alive:            finished = progress_without(make_threads, prefix, [paused], budget)            if len(finished) < len([tid for tid in range(len(make_threads({}))) if tid != paused]):                return True        if len(prefix) < depth:            return any(visit(prefix + [tid]) for tid in alive)        return False    return visit([])cs = lambda shared: [cas_increments(1)(shared), cas_increments(1)(shared)]at = lambda shared: [atomic_increments(1)(shared), atomic_increments(1)(shared)]print("spin lock:", is_blocking(lk))print("compare-and-swap:", is_blocking(cs))print("fetch-and-add:", is_blocking(at))
spin lock: True
compare-and-swap: False
fetch-and-add: False

Tanto el contador con comparar e intercambiar como el contador fetch-and-add son no bloqueantes. La escala tiene un tercer peldaño porque «sin cerrojos» aún permite un mal día a un hilo: puede perder todas las carreras.

03

Wait-free: todos terminan

Una operación wait-free tiene una cota sobre sus propios turnos que ningún vecino puede romper. worst_own_turns la mide: el mayor número de turnos que un hilo toma sobre todas las planificaciones completas. classify hace entonces la pregunta aguda. ¿El peor caso del hilo 0 se mantiene igual cuando su vecino tiene 1 incremento y cuando tiene 3? Si sí, el hilo 0 es wait-free. Si crece, el vecino puede hacer reintentar más al hilo 0, así que la estructura es solo lock-free.

main.pyPython
def worst_own_turns(make_threads, tid):    worst = 0    def visit(prefix):        nonlocal worst        _, alive = run_prefix(make_threads, prefix)        if not alive:            worst = max(worst, prefix.count(tid))            return        for other in alive:            visit(prefix + [other])    visit([])    return worstdef classify(factory, others=(1, 3)):    def program(m):        return lambda shared: [factory(1)(shared), factory(m)(shared)]    if is_blocking(program(others[0])):        return "blocking"    light, busy = worst_own_turns(program(others[0]), 0), worst_own_turns(program(others[1]), 0)    return "wait-free" if light == busy else "lock-free"for neighbour in (1, 2, 3):    cas = lambda shared: [cas_increments(1)(shared), cas_increments(neighbour)(shared)]    atomic = lambda shared: [atomic_increments(1)(shared), atomic_increments(neighbour)(shared)]    print(neighbour, "increments next door: CAS", worst_own_turns(cas, 0), "turns, fetch-and-add", worst_own_turns(atomic, 0))print([classify(f) for f in (locked_increments, cas_increments, atomic_increments)])
1 increments next door: CAS 4 turns, fetch-and-add 1
2 increments next door: CAS 6 turns, fetch-and-add 1
3 increments next door: CAS 8 turns, fetch-and-add 1
['blocking', 'lock-free', 'wait-free']
Cuál elegir

Wait-free es lo más fuerte y lo más caro de diseñar, y fetch-and-add muestra que no siempre está fuera de alcance. Lock-free es el objetivo práctico para colas y pilas. Un cerrojo está bien donde un titular pausado no puede dañarle, pero nunca en una ruta que no debe detenerse.

main.pyPython
returns = [float(r) for r in load_bars()["close"].pct_change().dropna()][:6]ups = [sum(1 for r in returns[start::2] if r > 0) for start in (0, 1)]for name, factory in (("locked", locked_increments), ("cas", cas_increments), ("atomic", atomic_increments)):    program = lambda shared, f=factory: [f(k)(shared) for k in ups]    blocking = is_blocking(program)    print(name, classify(factory), blocking, None if blocking else worst_own_turns(program, 0))
locked blocking True None
cas lock-free False 8
atomic wait-free False 1
Qué dicen los datos realeslos días de subida de los primeros 6 rendimientos de SPY: 1 incremento para el hilo 0, 3 para el hilo 1
Cerrojo de espera activablockingun hilo pausado detiene al otro
Comparar e intercambiarlock-freepeor caso del hilo 0: 8 turnos
Fetch-and-addwait-freepeor caso del hilo 0: 1 turno
Peor número de turnos propios del hilo 0 según el trabajo vecino
Fetch-and-add, 3 incrementos vecinos1
CAS, 1 incremento vecino4
CAS, 2 incrementos vecinos6
CAS, 3 incrementos vecinos8

Lea las barras: el hilo fetch-and-add necesita 1 turno por muy ocupado que esté su vecino, mientras que el hilo con comparar e intercambiar necesita 4, 6 y luego 8 turnos a medida que el vecino hace más incrementos. Cada incremento extra al lado puede hacer que el hilo 0 pierda una carrera más y repita su lectura y su intercambio. Ese crecimiento es exactamente lo que separa lock-free de wait-free.

DefiniciónGarantías de progreso

Bloqueante: un hilo pausado puede detener a los demás. Lock-free: algún hilo siempre termina. Wait-free: cada hilo termina en un número acotado de sus propios turnos. Cada nivel implica el anterior de esta lista.

04

Lo que va a construir

El laboratorio sigue la lección: el contador con cerrojo de espera activa, la ejecución con algunos hilos pausados, la prueba que dice si pausar un hilo puede detener a los demás, el peor caso de los turnos propios de un hilo, la clasificación de un contador y el estudio de los tres contadores sobre los días de subida de SPY.

  • Función · preguntaSe apoya en
  • locked_increments(k)¿Un contador que espera un cerrojo?Aritmética simple
  • progress_without(make_threads, prefix, paused, budget)¿Quién termina si algunos hilos están pausados?run_prefix
  • is_blocking(make_threads, depth, budget)¿Puede un hilo pausado detener a todos?progress_without
  • worst_own_turns(make_threads, tid)¿Cuántos turnos puede necesitar un hilo?run_prefix
  • classify(factory, others)¿Bloqueante, lock-free o wait-free?is_blockingworst_own_turns
  • study(bars)¿Dónde quedan los tres contadores en SPY?classifyis_blockingworst_own_turns

Ideas clave

  • Un cerrojo puede detener a todos. Pause al titular tras un turno y el otro hilo no termina nada en cincuenta rondas.
  • Lock-free significa que alguien siempre termina. Pero un hilo puede perder todas las carreras: comparar e intercambiar necesita 4, 6, 8 turnos cuando el vecino trabaja más.
  • Wait-free tiene una cota que ningún vecino rompe. Fetch-and-add termina en 1 turno haga lo que haga el vecino.
Comprobación rápida
Un hilo hace un incremento con comparar e intercambiar mientras otro hace 5 incrementos. ¿Qué afirmación está garantizada?
Elige una respuesta.

Siguiente tema: ya sabe lo que una cola podría prometer. Un búfer circular de un productor y un consumidor construye la cola sin cerrojos útil más simple, con un escritor y un lector, memoria fija y ninguna lectura-modificación-escritura atómica, y el explorador comprueba que nunca pierde ni reordena un tick.

Lab: qué se califica

6 tareas · 100 pts
  • Un contador con cerrojo de espera activa10 pts
  • Ejecutar con hilos pausados20 pts
  • Puede una pausa detener a todos20 pts
  • El peor caso de un hilo15 pts
  • Nombrar la garantía10 pts
  • El estudio con la tabla de SPY25 pts
Explorador · lab-lf-05
▾ lab-lf-05
▾ data
…
bash · en el navegador
learner@quantcraft:~/lab-lf-05$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

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