σμ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 AcademyRoadmapsRoteiros
Ctrl K
AO
+1,2 mil alunos
EntrarParticipar ↗
RoadmapsRoteiros
AO
+1,2 mil alunos
Entrar
  • 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 AcademyRoadmapsRoteiros
Ctrl K
AO
+1,2 mil alunos
EntrarParticipar ↗
RoadmapsRoteiros
AO
+1,2 mil alunos
Entrar
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Roteiro de Lock-free›Módulo 4 · Testes e segurança de memória›Tópico 4.2·~1 h 10 minEspecialista

Hazard pointers e reclamação por épocas

Posição no roteiroMódulo 4 de 4 · Tópico 2 de 3
4.2 Hazard pointers e reclamação por épocas
—Nota do lab— / 100XP total—Sequência—

4 min de leitura3 exemplos de código

  1. 01Liberar na hora: o pop inseguro
  2. 02Hazard pointers: anunciar e conferir
  3. 03Épocas: anunciar quando entrou
  4. 04O que você vai construir

Uma pilha sem travas nunca bloqueia, mas não pode simplesmente liberar um nó que removeu: outra thread pode ter lido o topo há um instante e estar prestes a ler o next desse nó. Memória liberada pode ser reutilizada ou desmapeada, então essa leitura é um uso após liberação. Há duas curas clássicas. Os hazard pointers deixam uma thread anunciar o nó que usa. As épocas deixam anunciar quando entrou. Em ambos, um nó desligado só é liberado quando ninguém mais pode segurá-lo.

01

Liberar na hora: o pop inseguro

O modelo é a pilha do tópico ABA. Uma thread lê o topo, lê o nó e depois troca o topo num único compare-and-swap. O pop inseguro libera o nó no mesmo turno em que a troca tem sucesso. Um nó liberado é um nó que ninguém pode tocar, e read_node registra um uso após liberação sempre que uma thread lê um. Duas threads que removem uma vez cada dão apenas 20 escalonamentos, e o explorador acha logo os ruins: aqueles em que uma thread leu o topo, a outra removeu e liberou esse nó e a primeira depois o lê.

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

Hazard pointers: anunciar e conferir

Um hazard pointer transforma a liberação numa conversa. Antes de ler um nó a thread escreve o número dele no seu espaço. Só isso não basta, porque o nó pode ter sido removido e liberado entre ler o topo e escrever o espaço, então a thread valida: lê o topo de novo e recomeça se ele mudou. Depois de um pop bem-sucedido a thread não libera o nó, põe-no na sua própria lista de aposentados e varre: lê uma vez o espaço de cada thread e libera só os nós aposentados que nenhum espaço cita. Os que restam na lista esperam uma varredura 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
DefiniçãoHazard pointer

Um anúncio por thread do único nó em leitura, escrito antes da leitura e validado depois. Um nó aposentado só é liberado se nenhum anúncio o citar.

03

Épocas: anunciar quando entrou

A reclamação por épocas anuncia muito menos. Há um contador global de época. Uma thread escreve a época atual no seu espaço ao entrar numa operação e -1 ao sair, e nunca anuncia nós individuais. Um nó aposentado na época e só pode estar com uma thread que entrou na época e ou antes, então é seguro liberá-lo quando toda thread dentro de uma operação anunciou uma época maior que e. O preço é que uma thread que fica muito tempo dentro segura todos os nós aposentados desde que entrou, enquanto um hazard pointer segura só o nó que cita.

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
Um vazamento não é um bug, uma thread travada é

Os dois esquemas deixam nós aposentados esperando no fim de alguns escalonamentos: o explorador os informa como pending. Uma varredura posterior os libera, então nada se perde. O perigo real das épocas é uma thread que para dentro de uma operação: ela nunca anuncia uma época mais nova, nenhum nó aposentado depois dela é liberado e a memória cresce. Os hazard pointers são imunes, porque uma thread parada segura só um nó.

O que dizem os dados reaisduas threads removendo uma vez cada de uma pilha dos dois primeiros fechamentos do SPY (9369 e 9305 centavos)
Liberar na hora206 escalonamentos leem um nó liberado
Hazard pointers12,122nenhum lê um nó liberado
Épocas9,012nenhum lê um nó liberado
Nós deixados esperando1no máximo, nos dois esquemas seguros

A segurança custa escalonamentos, porque cada anúncio, validação e varredura é mais um passo em que a outra thread pode se infiltrar: 20 virou 12 122 e 9012. É exatamente por isso que o explorador é útil aqui. Ele não precisa saber por que o esquema está certo, verifica cada um desses escalonamentos.

04

O que você vai construir

O laboratório segue a aula: o estado compartilhado com um conjunto de espaços por thread, o pop inseguro, o pop com hazard pointers, o pop com épocas, a verificação dos dois invariantes (nenhum uso após liberação, nada perdido), o verificador que explora todos os escalonamentos de um esquema e o estudo dos três esquemas em fechamentos do SPY.

  • Função · perguntaBaseia-se em
  • make_heap(values, threads)O que é compartilhado?Aritmética simples
  • pop_unsafe(shared, tid)Remover e liberar na horaAritmética simples
  • pop_hazard(shared, tid)Anunciar, validar, aposentar, varrerAritmética simples
  • pop_epoch(shared, tid)Entrar, aposentar com a época, reclamarAritmética simples
  • violations(final)Quais invariantes quebraram?Aritmética simples
  • verify(scheme, values, pops)Um esquema é seguro sob todos os escalonamentos?explore_allviolations
  • study(bars)Qual esquema sobrevive à pilha do SPY?verify

Pontos-chave

  • Liberar é um protocolo. Desligar um nó não o torna seguro para liberar: alguém ainda pode estar prestes a lê-lo.
  • Um hazard pointer cita um nó; uma época cita um momento. Hazard pointers exigem validação após o anúncio; épocas são mais baratas, mas uma thread parada segura tudo.
  • Deixe o explorador achar a brecha. Remova a validação e escalonamentos dos 12 122 passam a ler memória liberada.
Verificação rápida
A thread A leu o topo (nó 0) mas ainda não o anunciou. A thread B remove o nó 0 e varre. O que a thread A deve fazer depois de anunciar o nó 0?
Escolha uma resposta.

Próximo tópico: isto era uma pilha, e de brinquedo. O projeto final uma distribuição de dados de mercado sem travas coloca a habilidade inteira para trabalhar: ticks do SPY vão de um produtor a vários consumidores por uma estrutura sem travas, e o explorador prova seus invariantes sob todos os escalonamentos.

Lab: o que é avaliado

7 tarefas · 100 pts
  • O estado compartilhado5 pts
  • Liberar na hora10 pts
  • Hazard pointers20 pts
  • Épocas20 pts
  • Os invariantes10 pts
  • O verificador15 pts
  • O estudo com a tabela do SPY20 pts
Explorador · lab-lf-10
▾ lab-lf-10
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-lf-10$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

Tudo em lab-lf-10 volta aos arquivos iniciais e o terminal é limpo. Sua solução atual será perdida — isso não pode ser desfeito.

Será redefinido
  • main.py → código inicial
  • Histórico do terminal e saída dos testes
  • Alterações não enviadas
Será mantido
  • Melhor nota do lab
  • Seu XP e sua sequência
  • Progresso da aula e anotações