σμ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 3 · Filas sem travas›Tópico 3.2·~55 minAvançado

O problema ABA e os ponteiros etiquetados

Posição no roteiroMódulo 3 de 4 · Tópico 2 de 3
3.2 O problema ABA e os ponteiros etiquetados
—Nota do lab— / 100XP total—Sequência—

5 min de leitura4 exemplos de código

  1. 01Uma pilha com compara-e-troca
  2. 02O escalonamento ABA
  3. 03Uma etiqueta que conta as mudanças
  4. 04O que você vai construir

Compara-e-troca pergunta «o valor ainda é o que li?» e não consegue perguntar «foi mudado e mudado de volta?». Numa pilha sem travas esse ponto cego é o problema ABA: uma troca tem sucesso sobre uma ideia obsoleta da pilha e devolve um valor duas vezes. De 3021 escalonamentos de um pop contra uma thread que mexe na pilha, 21 corrompem a pilha simples e nenhum corrompe a etiquetada.

01

Uma pilha com compara-e-troca

Uma pilha sem travas guarda seus nós no dicionário compartilhado: o nó i tem um valor val{i} e um elo next{i} para o nó de baixo, e head nomeia o topo (-1 se vazia). Para empilhar um nó, lê-se a cabeça, aponta-se o elo do novo nó para ela e troca-se a cabeça do valor lido para o novo nó; se a troca falha, alguém mudou a cabeça, então recomeça-se. Para desempilhar, lê-se a cabeça, lê-se o elo e o valor do nó do topo e troca-se a cabeça do nó lido para o seu elo. O pop vencedor registra seu valor em popped. Cada operação da pilha é um gerador, e uma thread a executa com yield from, que também devolve o valor retornado.

main.pyPython
def make_stack(values, tagged=False):    top = 0 if values else -1    shared = {"head": (top, 0) if tagged else top, "popped": ()}    for i, value in enumerate(values):        shared["val%d" % i] = value        shared["next%d" % i] = i + 1 if i + 1 < len(values) else -1    return shareddef push(shared, node, value, tagged=False):    shared["val%d" % node] = value    while True:        yield        old = shared["head"]        yield        shared["next%d" % node] = old[0] if tagged else old        yield        new = (node, old[1] + 1) if tagged else node        if compare_and_swap(shared, "head", old, new):            returndef pop(shared, tagged=False):    while True:        yield        old = shared["head"]        idx = old[0] if tagged else old        if idx == -1:            return None        yield        nxt = shared["next%d" % idx]        value = shared["val%d" % idx]        yield        new = (nxt, old[1] + 1) if tagged else nxt        if compare_and_swap(shared, "head", old, new):            shared["popped"] = shared["popped"] + (value,)            return valuedef alone(shared):    def thread():        for _ in range(3):            print("popped", (yield from pop(shared)))    return [thread()]final, alive = run_prefix(alone, [0] * 20, make_stack([10, 20]))print(final["head"], final["popped"])
popped 10
popped 20
popped None
-1 (10, 20)

Sozinha, a pilha é uma pilha comum: os pops devolvem 10, 20 e depois None, e a cabeça volta a -1. Tudo depende do que acontece quando outra thread trabalha entre as três leituras de um pop e a sua troca.

02

O escalonamento ABA

Aqui está a armadilha. Uma thread vítima começa um pop: lê head = 0 (nó A) e o elo next0 = 1 (nó B) e é pausada antes da sua troca. Uma thread agitadora então desempilha A, desempilha B e empilha o nó 0 de volta com um valor novo 99, de modo que head volta a ser 0, sem nada embaixo. A vítima acorda. Seu compara-e-troca só pergunta se head ainda é 0, e é, então a troca tem sucesso e instala o nó 1 como cabeça, um nó que não está mais na pilha. Seguimos o escalonamento [0, 0], depois nove turnos da agitadora, depois a vítima, e verificamos o resultado com conserved: os valores retirados mais os que restam na pilha devem ser exatamente os valores que foram colocados.

main.pyPython
def walk(shared, tagged=False):    values = []    idx = shared["head"][0] if tagged else shared["head"]    while idx != -1 and len(values) < 10:        values.append(shared["val%d" % idx])        idx = shared["next%d" % idx]    return valuesdef conserved(shared, expected, tagged=False):    return sorted(list(shared["popped"]) + walk(shared, tagged)) == sorted(expected)def aba_program(tagged=False):    def make_threads(shared):        def victim():            yield from pop(shared, tagged)        def churner():            yield from pop(shared, tagged)            yield from pop(shared, tagged)            yield from push(shared, 0, 99, tagged)        return [victim(), churner()]    return make_threadsprefix = [0, 0] + [1] * 9 + [0]final, alive = run_prefix(aba_program(), prefix, make_stack([10, 20]))print(final)print("still on the stack:", walk(final), "| conserved:", conserved(final, [10, 20, 99]))
{'head': 1, 'popped': (10, 20, 10), 'val0': 99, 'next0': -1, 'val1': 20, 'next1': -1}
still on the stack: [20] | conserved: False

A vítima devolveu 10, que a agitadora já havia retirado (popped: (10, 20, 10)), a cabeça é o nó 1 embora o valor 20 também tenha sido retirado, então 20 está na pilha e em popped, e o novo valor 99 se perde. conserved diz False. Uma atualização perdida, um valor duplicado e um nó que não deveria existir: tudo por uma troca «correta» para o valor que comparou.

DefiniçãoProblema ABA

Um valor é lido como A, mudado para B e mudado de volta para A antes de um compara-e-troca verificá-lo. A troca vê A e tem sucesso, embora o estado em volta do valor tenha mudado. Acontece quando um nó pode ser liberado e seu endereço reutilizado.

03

Uma etiqueta que conta as mudanças

A correção é fazer a cabeça carregar sua própria história. Na versão com ponteiro etiquetado head é um par (nó, etiqueta), e cada push ou pop bem-sucedido o troca por (novo nó, etiqueta + 1). O compara-e-troca agora compara os dois campos, então a cabeça (0, 3) deixada pela agitadora não é o (0, 0) lido pela vítima. A troca da vítima falha, ela conta uma nova tentativa, lê a pilha real e retira 99. O mesmo escalonamento, com a etiqueta, termina corretamente.

main.pyPython
final, alive = run_prefix(aba_program(True), prefix, make_stack([10, 20], True))print(final["head"], "victim alive:", alive == [0])final, alive = run_prefix(aba_program(True), prefix + [0] * 6, make_stack([10, 20], True))print(final["popped"], walk(final, True), "| conserved:", conserved(final, [10, 20, 99], True))
(0, 3) victim alive: True
(10, 20, 99) [] | conserved: True
O hardware real faz o mesmo com bits livres

Um ponteiro de 64 bits raramente precisa de todos os seus bits, então uma etiqueta pode viver nos 16 superiores, ou um compara-e-troca de largura dupla pode trocar juntos um ponteiro e um contador. Uma etiqueta que dá a volta pode em teoria se repetir; o outro remédio, a reclamação segura de memória, impede que um nó seja reutilizado enquanto alguém ainda pode lê-lo, e é assunto de um tópico posterior.

main.pyPython
values = [int(round(float(c) * 100)) for c in load_bars()["close"].iloc[:2]]for tagged in (False, True):    outcomes = explore_all(aba_program(tagged), make_stack(values, tagged))    corrupt = sum(n for key, n in outcomes.items() if not conserved(dict(key), values + [99], tagged))    print("tagged" if tagged else "plain ", sum(outcomes.values()), "schedules,", corrupt, "corrupt")
plain  3021 schedules, 21 corrupt
tagged 3021 schedules, 0 corrupt
O que dizem os dados reaisuma pilha dos dois primeiros fechamentos do SPY (9369 e 9305 centavos), um pop contra uma agitadora
Escalonamentos3,021explorados para cada pilha
Pilha simples21escalonamentos corrompidos: valor duplicado ou perdido
Pilha etiquetada0escalonamentos corrompidos

Vinte e um escalonamentos em 3021 é menos de 1%. Um teste de estresse numa máquina real poderia rodar dias sem tocar em nenhum e depois falhar uma vez em produção. O explorador lista todos, e a etiqueta elimina todos.

04

O que você vai construir

O laboratório segue a aula: a pilha no dicionário compartilhado, push e pop com laço de compara-e-troca (simples e etiquetada), um percurso da pilha, a verificação de conservação, o programa ABA de uma vítima e uma agitadora, e o estudo das duas pilhas sobre todos os escalonamentos com dois fechamentos do SPY.

  • Função · perguntaBaseia-se em
  • make_stack(values, tagged)Como é a pilha compartilhada?Aritmética simples
  • push(shared, node, value, tagged)Como um nó é empilhado?Aritmética simples
  • pop(shared, tagged)Como um valor é retirado?Aritmética simples
  • walk(shared, tagged)O que há na pilha?Aritmética simples
  • conserved(shared, expected, tagged)Algo foi perdido ou duplicado?walk
  • aba_program(tagged)Como é a armadilha ABA?poppush
  • study(bars)Quantos escalonamentos a etiqueta salva?make_stackaba_programconserved

Pontos-chave

  • Compara-e-troca compara valores, não história. Uma cabeça que vai A, B, A parece inalterada, e a troca tem sucesso numa pilha que mudou.
  • 21 de 3021 escalonamentos bastam. Um bug abaixo de 1% nunca aparece num teste de estresse e sempre aparece num explorador.
  • Uma etiqueta transforma A, B, A em A0, B1, A2. O par da cabeça muda a cada troca bem-sucedida, então uma troca obsoleta sempre falha.
Verificação rápida
Uma cabeça etiquetada é o par (nó, etiqueta), e cada mudança bem-sucedida sobe a etiqueta em 1. A vítima leu (0, 0). A agitadora fez então três mudanças bem-sucedidas (pop, pop, push). Qual é a cabeça agora, e a troca da vítima tem sucesso?
Escolha uma resposta.

Próximo tópico: uma pilha é uma cabeça. Uma fila precisa de duas pontas e muitas threads em cada uma. Filas de vários produtores e vários consumidores constroem uma fila limitada em que produtores e consumidores reservam um tíquete com fetch-and-add e cada casa carrega um número de sequência, e o explorador a verifica sobre todos os escalonamentos.

Lab: o que é avaliado

6 tarefas · 100 pts
  • A pilha compartilhada10 pts
  • Empilhar15 pts
  • Desempilhar20 pts
  • Percorrer e verificar20 pts
  • O programa ABA10 pts
  • O estudo com a tabela do SPY25 pts
Explorador · lab-lf-07
▾ lab-lf-07
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-lf-07$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

Tudo em lab-lf-07 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