σμ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.1·~55 minAvançado

Testar código concorrente: detectores de corrida e verificação de modelos

Posição no roteiroMódulo 4 de 4 · Tópico 1 de 3
4.1 Testar código concorrente: detectores de corrida e verificação de modelos
—Nota do lab— / 100XP total—Sequência—

6 min de leitura4 exemplos de código

  1. 01Um trace e uma pergunta
  2. 02Lockset: qual trava guarda esta variável?
  3. 03Happens-before: o que poderia realmente se sobrepor
  4. 04Qual é o tamanho do espaço?
  5. 05O que você vai construir

Executar todos os escalonamentos prova um programa pequeno, mas custa o número de intercalações. As ferramentas reais tomam dois atalhos. Um detector de corridas observa uma execução e informa as variáveis que poderiam competir em outro escalonamento. Um verificador de modelos lembra dos estados vistos. Para duas threads de 10 passos são 184 756 escalonamentos contra 121 estados.

01

Um trace e uma pergunta

Um detector de corridas não executa threads. Ele lê um trace: cada leitura e escrita de dados compartilhados e cada aquisição e liberação de uma trava. Nossos programas viram listas dessas operações, e interleave transforma um programa e um escalonamento no trace daquela execução. Duas threads que leem e escrevem x cada uma sem trava dão os quatro eventos abaixo. Neste escalonamento a thread 0 termina antes de a thread 1 começar, então um teste veria a resposta certa. A pergunta do detector é outra: as duas threads poderiam ter se sobreposto?

main.pyPython
def interleave(programs, schedule):    position = [0] * len(programs)    trace = []    for tid in schedule:        kind, name = programs[tid][position[tid]]        position[tid] += 1        trace.append((tid, kind, name))    return traceunlocked = [[("read", "x"), ("write", "x")], [("read", "x"), ("write", "x")]]trace = interleave(unlocked, [0, 0, 1, 1])print(trace)
[(0, 'read', 'x'), (0, 'write', 'x'), (1, 'read', 'x'), (1, 'write', 'x')]
02

Lockset: qual trava guarda esta variável?

O primeiro detector usa uma disciplina: toda variável compartilhada deveria ser guardada por alguma trava mantida em cada acesso. A regra do lockset acompanha as travas que cada thread mantém e, para cada variável, intersecta os conjuntos de travas mantidos em seus acessos. Se pelo menos duas threads tocaram a variável, uma a escreveu e a interseção é vazia, nenhuma trava única a protege: reporta-se uma corrida potencial. O contador sem trava é sinalizado; o com trava, não. A regra é barata e pode dar um falso alarme. Na passagem abaixo a thread 0 escreve x e depois o entrega à thread 1 pela trava. Nenhuma trava era mantida na escrita, então a regra reclama, embora os acessos estejam na verdade ordenados.

main.pyPython
def lockset_races(trace):    held = {}    accesses = {}    for tid, kind, name in trace:        locks = held.setdefault(tid, set())        if kind == "acquire":            locks.add(name)        elif kind == "release":            locks.discard(name)        else:            accesses.setdefault(name, []).append((tid, kind == "write", frozenset(locks)))    racy = []    for name, history in accesses.items():        threads = {tid for tid, _, _ in history}        writes = any(is_write for _, is_write, _ in history)        common = frozenset.intersection(*(locks for _, _, locks in history))        if len(threads) >= 2 and writes and not common:            racy.append(name)    return sorted(racy)locked = [[("acquire", "m"), ("read", "x"), ("write", "x"), ("release", "m")]] * 2guarded = interleave(locked, [0, 0, 0, 0, 1, 1, 1, 1])handoff = [(0, "write", "x"), (0, "acquire", "m"), (0, "release", "m"), (1, "acquire", "m"), (1, "release", "m"), (1, "read", "x")]print("unlocked:", lockset_races(trace))print("locked:  ", lockset_races(guarded))print("handoff: ", lockset_races(handoff))
unlocked: ['x']
locked:   []
handoff:  ['x']
DefiniçãoRegra lockset do Eraser

Reportar uma variável quando duas threads a acessam, uma escreve e nenhuma trava é mantida em todos os seus acessos. Acha a falta de disciplina de travas a partir de uma execução, com falsos alarmes onde a ordem vem de algo que não uma trava comum.

03

Happens-before: o que poderia realmente se sobrepor

O segundo detector faz a pergunta exata: existe uma cadeia de ordens do primeiro acesso ao segundo? Há dois tipos de aresta, a ordem dentro de uma thread e o par liberação e depois aquisição da mesma trava. A relação happens-before é o fecho de ambas. Um relógio vetorial por thread registra o que a thread viu; uma liberação guarda o relógio na trava e uma aquisição toma o máximo componente a componente com ele. Um acesso é uma corrida se um acesso anterior conflitante de outra thread tem uma época que o relógio atual não viu. Na passagem, a liberação e a aquisição ligam a escrita à leitura, então não há corrida. Ele só reporta corridas reais, mas apenas nos acessos que observou.

main.pyPython
def hb_races(trace):    clocks = {}    lock_clocks = {}    history = {}    racy = set()    for tid, kind, name in trace:        clock = clocks.setdefault(tid, {tid: 1})        if kind == "acquire":            for u, value in lock_clocks.get(name, {}).items():                clock[u] = max(clock.get(u, 0), value)        elif kind == "release":            lock_clocks[name] = dict(clock)            clock[tid] += 1        else:            is_write = kind == "write"            for u, was_write, epoch in history.get(name, []):                if u != tid and (is_write or was_write) and clock.get(u, 0) < epoch:                    racy.add(name)            history.setdefault(name, []).append((tid, is_write, clock[tid]))    return sorted(racy)print("unlocked:", hb_races(trace))print("locked:  ", hb_races(guarded))print("handoff: ", hb_races(handoff))
unlocked: ['x']
locked:   []
handoff:  []
Use os dois, nesta ordem

Ferramentas reais como o ThreadSanitizer usam happens-before porque não dá falsos alarmes no trace que vê. A regra do lockset é a forma mais barata de achar uma variável que ninguém guarda nunca, e uma boa primeira verificação. Nenhuma das duas substitui um verificador de modelos para uma estrutura sem travas, cuja correção não vem de travas de modo algum.

04

Qual é o tamanho do espaço?

Um verificador de modelos visita o mesmo programa, mas lembra estados. Nos nossos programas um estado é só o quanto cada thread avançou, então com threads de n passos há (n1 + 1) × (n2 + 1) × ... estados, quantos forem os escalonamentos que levam a eles. As threads contadoras abaixo dão os números para os dados reais, com os escalonamentos e os estados lado a lado.

main.pyPython
def count_states(lengths):    total = 1    for n in lengths:        total *= n + 1    return totaldef counter_programs(ups, locked):    programs = []    for count in ups:        program = []        for _ in range(count):            program += ([("acquire", "m")] if locked else []) + [("read", "x"), ("write", "x")] + ([("release", "m")] if locked else [])        programs.append(program)    return programsreturns = [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, lock in (("unlocked", False), ("locked", True)):    programs = counter_programs(ups, lock)    lengths = [len(p) for p in programs]    trace = interleave(programs, serial_schedule(lengths))    print(name, len(trace), "events", lockset_races(trace), hb_races(trace), count_schedules(lengths), "schedules,", count_states(lengths), "states")
unlocked 8 events ['x'] ['x'] 28 schedules, 21 states
locked 16 events [] [] 1820 schedules, 65 states
O que dizem os dados reaisduas threads contando os dias de alta dos primeiros 6 retornos do SPY (1 e 3)
Contador sem travaxsinalizado pelos dois detectores
Contador com travanonesinalizado por nenhum
Escalonamentos do programa com trava1,820para verificar um a um
Estados do programa com trava65para lembrar ao todo

Um único trace serial basta para os dois detectores acharem a trava que falta, e o programa com trava está limpo. A vantagem do verificador de modelos sobre a força bruta é o fator entre 1820 e 65, e cresce sem limite: para 10 e 10 passos são 184 756 contra 121. Por isso a verificação de modelos pode cobrir programas que a enumeração simples nunca alcançaria, enquanto um detector de corridas custa uma passada sobre uma execução.

DefiniçãoRedução do espaço de estados

Lembrar um estado na primeira vez que é alcançado e nunca expandi-lo duas vezes. Muitos escalonamentos chegam ao mesmo estado, então o custo cai do número de escalonamentos para o número de estados.

05

O que você vai construir

O laboratório segue a aula: o trace de uma execução, a regra do lockset, a regra happens-before com relógios vetoriais, a contagem de estados, os programas das threads contadoras e o estudo dos dois detectores e dos dois tamanhos de espaço nos dias de alta do SPY.

  • Função · perguntaBaseia-se em
  • interleave(programs, schedule)O que aconteceu, em que ordem?Aritmética simples
  • lockset_races(trace)Quais variáveis não têm trava comum?Aritmética simples
  • hb_races(trace)Quais variáveis têm acessos não ordenados?Aritmética simples
  • count_states(lengths)Quantos estados é preciso lembrar?Aritmética simples
  • counter_programs(ups, locked)O que fazem as threads contadoras?Aritmética simples
  • study(bars)Os detectores concordam no SPY?counter_programsinterleavelockset_raceshb_racescount_states

Pontos-chave

  • Um detector lê uma execução. Um trace serial em que nada deu errado ainda revela a trava que falta.
  • Lockset é barato e barulhento, happens-before é exato. Uma passagem por trava é falso alarme para o lockset e ordenada para os relógios vetoriais.
  • Lembre estados, não escalonamentos. 1820 escalonamentos contra 65 estados, e 184 756 contra 121 para duas threads de 10 passos.
Verificação rápida
A thread 0 escreve x e depois libera a trava m; a thread 1 adquire m e depois lê x. A escrita vem antes da liberação. O detector de relógios vetoriais reporta uma corrida em x?
Escolha uma resposta.

Próximo tópico: o código sem travas tem mais um perigo que nenhum detector ou verificador consegue consertar depois: quando uma thread libera um nó que outra thread ainda está lendo. Hazard pointers e reclamação por épocas constroem as duas formas clássicas de liberar memória com segurança, e o explorador mostra um uso após liberação sob cada escalonamento que você não impediu.

Lab: o que é avaliado

6 tarefas · 100 pts
  • O trace de uma execução10 pts
  • A regra do lockset25 pts
  • Happens-before com relógios vetoriais30 pts
  • Os estados de um verificador de modelos5 pts
  • As threads contadoras5 pts
  • O estudo com a tabela do SPY25 pts
Explorador · lab-lf-09
▾ lab-lf-09
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-lf-09$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

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