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

Punteros de peligro y recuperación por épocas

Posición en la rutaMódulo 4 de 4 · Tema 2 de 3
4.2 Punteros de peligro y recuperación por épocas
—Puntuación del lab— / 100XP total—Racha—

5 min de lectura3 ejemplos de código

  1. 01Liberar de inmediato: el pop inseguro
  2. 02Punteros de peligro: anunciar y comprobar
  3. 03Épocas: anunciar cuándo entraste
  4. 04Lo que va a construir

Una pila sin cerrojos nunca bloquea, pero no puede simplemente liberar un nodo que ha sacado: otro hilo puede haber leído la cabeza hace un instante y estar a punto de leer el next de ese nodo. La memoria liberada puede reutilizarse o desasignarse, así que esa lectura es un uso tras liberar. Hay dos curas clásicas. Los punteros de peligro dejan que un hilo anuncie el nodo que usa. Las épocas le dejan anunciar cuándo entró. En ambos, un nodo desenlazado solo se libera cuando nadie puede seguir teniéndolo.

01

Liberar de inmediato: el pop inseguro

El modelo es la pila del tema ABA. Un hilo lee la cabeza, lee el nodo y luego cambia la cabeza en un solo compare-and-swap. El pop inseguro libera el nodo en el mismo turno en que el cambio tiene éxito. Un nodo liberado es un nodo que nadie puede tocar, y read_node registra un uso tras liberar cada vez que un hilo lee uno. Dos hilos que sacan una vez cada uno dan solo 20 planificaciones, y el explorador encuentra enseguida las malas: aquellas en que un hilo leyó la cabeza, el otro sacó y liberó ese nodo y el primero lo lee después.

main.pyPython
def pop_unsafe(shared, tid):    while True:        yield        idx = shared["head"]        if idx == -1:            return None        yield        nxt, value = read_node(shared, idx)        yield        if compare_and_swap(shared, "head", idx, nxt):            shared["popped"] = shared["popped"] + (value,)            free_node(shared, idx)            return valuedef two_pops(shared):    return [pop_unsafe(shared, 0), pop_unsafe(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])print(sum(outcomes.values()), "schedules,", hits, "of them read a freed node")
20 schedules, 6 of them read a freed node
02

Punteros de peligro: anunciar y comprobar

Un puntero de peligro convierte la liberación en una conversación. Antes de leer un nodo el hilo escribe su número en su ranura. Eso solo no basta, porque el nodo puede haberse sacado y liberado entre leer la cabeza y escribir la ranura, así que el hilo valida: vuelve a leer la cabeza y empieza de nuevo si se movió. Tras un pop con éxito el hilo no libera el nodo, lo pone en su propia lista de retirados y barre: lee una vez la ranura de cada hilo y libera solo los nodos retirados que ninguna ranura nombra. Los que quedan en la lista esperan a un barrido posterior.

main.pyPython
def pop_hazard(shared, tid):    while True:        yield        idx = shared["head"]        if idx == -1:            shared["hp%d" % tid] = -1            return None        yield        shared["hp%d" % tid] = idx        yield        if shared["head"] != idx:            continue        yield        nxt, value = read_node(shared, idx)        yield        if compare_and_swap(shared, "head", idx, nxt):            shared["popped"] = shared["popped"] + (value,)            shared["hp%d" % tid] = -1            shared["retired%d" % tid] = shared["retired%d" % tid] + (idx,)            break    yield    hazards = tuple(shared["hp%d" % t] for t in range(shared["threads"]))    yield    keep = []    for node in shared["retired%d" % tid]:        if node in hazards:            keep.append(node)        else:            free_node(shared, node)    shared["retired%d" % tid] = tuple(keep)    return valuedef two_pops(shared):    return [pop_hazard(shared, 0), pop_hazard(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])waiting = max(sum(len(dict(key)["retired%d" % t]) for t in (0, 1)) for key in outcomes)print(sum(outcomes.values()), "schedules,", hits, "read a freed node, up to", waiting, "node waiting at the end")
12122 schedules, 0 read a freed node, up to 1 node waiting at the end
DefiniciónPuntero de peligro

Un anuncio por hilo del único nodo que se está leyendo, escrito antes de la lectura y validado después. Un nodo retirado solo se libera si ningún anuncio lo nombra.

03

Épocas: anunciar cuándo entraste

La recuperación por épocas anuncia mucho menos. Hay un contador global de época. Un hilo escribe la época actual en su ranura al entrar en una operación y -1 al salir, y nunca anuncia nodos individuales. Un nodo retirado en la época e solo puede estar en manos de un hilo que entró en la época e o antes, así que es seguro liberarlo cuando cada hilo dentro de una operación anunció una época mayor que e. El precio es que un hilo que permanece mucho tiempo dentro retiene todos los nodos retirados desde que entró, mientras que un puntero de peligro retiene solo el nodo que nombra.

main.pyPython
def pop_epoch(shared, tid):    yield    shared["ep%d" % tid] = shared["epoch"]    while True:        yield        idx = shared["head"]        if idx == -1:            shared["ep%d" % tid] = -1            return None        yield        nxt, value = read_node(shared, idx)        yield        if compare_and_swap(shared, "head", idx, nxt):            shared["popped"] = shared["popped"] + (value,)            shared["ep%d" % tid] = -1            shared["retired%d" % tid] = shared["retired%d" % tid] + ((idx, shared["epoch"]),)            shared["epoch"] = shared["epoch"] + 1            break    announced = []    for t in range(shared["threads"]):        yield        if shared["ep%d" % t] != -1:            announced.append(shared["ep%d" % t])    yield    keep = []    for node, epoch in shared["retired%d" % tid]:        if announced and epoch >= min(announced):            keep.append((node, epoch))        else:            free_node(shared, node)    shared["retired%d" % tid] = tuple(keep)    return valuedef two_pops(shared):    return [pop_epoch(shared, 0), pop_epoch(shared, 1)]outcomes = explore_all(two_pops, make_heap([9369, 9305]))hits = sum(n for key, n in outcomes.items() if dict(key)["uaf"])waiting = max(sum(len(dict(key)["retired%d" % t]) for t in (0, 1)) for key in outcomes)print(sum(outcomes.values()), "schedules,", hits, "read a freed node, up to", waiting, "node waiting at the end")
9012 schedules, 0 read a freed node, up to 1 node waiting at the end
Una fuga no es un fallo, un hilo atascado sí

Ambos esquemas dejan nodos retirados esperando al final de algunas planificaciones: el explorador los informa como pending. Un barrido posterior los libera, así que nada se pierde. El peligro real de las épocas es un hilo que se queda parado dentro de una operación: nunca anuncia una época posterior, ningún nodo retirado después se libera jamás y la memoria crece. Los punteros de peligro son inmunes, porque un hilo parado retiene solo un nodo.

Qué dicen los datos realesdos hilos que sacan una vez cada uno de una pila de los dos primeros cierres de SPY (9369 y 9305 centavos)
Liberar de inmediato206 planificaciones leen un nodo liberado
Punteros de peligro12,122ninguna lee un nodo liberado
Épocas9,012ninguna lee un nodo liberado
Nodos en espera1como máximo, en los dos esquemas seguros

La seguridad cuesta planificaciones, porque cada anuncio, validación y barrido es un paso más en el que el otro hilo puede colarse: 20 pasó a 12 122 y 9012. Por eso el explorador es útil aquí. No necesita saber por qué el esquema es correcto, comprueba cada una de esas planificaciones.

04

Lo que va a construir

El laboratorio sigue la lección: el estado compartido con un juego de ranuras por hilo, el pop inseguro, el pop con punteros de peligro, el pop con épocas, la comprobación de los dos invariantes (ningún uso tras liberar, nada perdido), el verificador que explora todas las planificaciones de un esquema y el estudio de los tres esquemas sobre cierres de SPY.

  • Función · preguntaSe apoya en
  • make_heap(values, threads)¿Qué se comparte?Aritmética simple
  • pop_unsafe(shared, tid)Sacar y liberar de inmediatoAritmética simple
  • pop_hazard(shared, tid)Anunciar, validar, retirar, barrerAritmética simple
  • pop_epoch(shared, tid)Entrar, retirar con la época, recuperarAritmética simple
  • violations(final)¿Qué invariantes se rompieron?Aritmética simple
  • verify(scheme, values, pops)¿Es seguro un esquema bajo todas las planificaciones?explore_allviolations
  • study(bars)¿Qué esquema sobrevive a la pila de SPY?verify

Ideas clave

  • Liberar es un protocolo. Desenlazar un nodo no lo hace seguro de liberar: alguien puede estar a punto de leerlo.
  • Un puntero de peligro nombra un nodo; una época nombra un momento. Los punteros de peligro necesitan validación tras el anuncio; las épocas son más baratas pero un hilo parado retiene todo.
  • Deje que el explorador encuentre el hueco. Quite la validación y planificaciones de las 12 122 leen memoria liberada.
Comprobación rápida
El hilo A ha leído la cabeza (nodo 0) pero aún no la ha anunciado. El hilo B saca el nodo 0 y barre. ¿Qué debe hacer el hilo A tras anunciar el nodo 0?
Elige una respuesta.

Siguiente tema: esto era una pila, y de juguete. El proyecto final una difusión de datos de mercado sin cerrojos pone a trabajar toda la habilidad: los ticks de SPY van de un productor a varios consumidores por una estructura sin cerrojos, y el explorador demuestra sus invariantes bajo todas las planificaciones.

Lab: qué se califica

7 tareas · 100 pts
  • El estado compartido5 pts
  • Liberar de inmediato10 pts
  • Punteros de peligro20 pts
  • Épocas20 pts
  • Los invariantes10 pts
  • El verificador15 pts
  • El estudio con la tabla de SPY20 pts
Explorador · lab-lf-10
▾ lab-lf-10
▾ data
…
bash · en el navegador
learner@quantcraft:~/lab-lf-10$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

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