σμ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 2 · Atômicas, ordem e garantias›Tópico 2.3·~45 minAvançado

Garantias de progresso: de obstruction-free a wait-free

Posição no roteiroMódulo 2 de 4 · Tópico 3 de 3
2.3 Garantias de progresso: de obstruction-free a wait-free
—Nota do lab— / 100XP total—Sequência—

5 min de leitura4 exemplos de código

  1. 01Uma thread em pausa
  2. 02Sem travas: alguém sempre termina
  3. 03Wait-free: todos terminam
  4. 04O que você vai construir

Três contadores dão a resposta certa sob todos os escalonamentos e, mesmo assim, não são iguais. Pause uma thread no pior momento: a trava giratória para todo mundo, o contador com compara-e-troca deixa alguém passar e o contador fetch-and-add deixa cada thread terminar em 1 turno. São os três degraus da escada das garantias de progresso.

01

Uma thread em pausa

Um sistema operacional real pode tirar uma thread do seu núcleo a qualquer momento: sua fatia de tempo acaba, uma falha de página a manda ao disco, chega uma interrupção. Uma boa estrutura concorrente deve sobreviver a isso. Precisamos a pergunta com um adversário. Ele executa um prefix de turnos à sua escolha, depois pausa uma thread para sempre e damos um budget de rodadas a todas as outras. Se alguma thread não pausada ainda não terminou, a estrutura pode ser parada. Uma trava giratória falha nesse teste do jeito mais simples: se o adversário pausa a thread que segura a trava, as outras giram até o orçamento acabar.

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]

Depois de um turno a thread 0 segura a trava ('lock': 1). Pausada ali, a thread 1 não termina nada em cinquenta rodadas: []. Deixe a thread 0 fazer antes seus quatro turnos e a trava volta a ficar livre, então a thread 1 termina: [1]. A diferença é só onde o adversário ataca. Uma trava transforma qualquer pausa dentro da seção crítica numa pausa do programa inteiro.

02

Sem travas: alguém sempre termina

is_blocking automatiza o adversário. Tenta todos os prefixos de alguns turnos, pausa cada thread viva por vez e pergunta a progress_without se as outras ainda terminam. Para a trava giratória a resposta é sim depois de um único turno. Para o contador com compara-e-troca nunca: uma thread pausada entre a leitura e a troca simplesmente deixa o valor inalterado, então a troca da outra thread tem sucesso. Esta é a definição de sem travas: o sistema como um todo sempre avança, mesmo se uma thread estiver parada para sempre.

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 o contador com compara-e-troca quanto o contador fetch-and-add são não bloqueantes. A escada tem um terceiro degrau porque «sem travas» ainda permite um dia ruim para uma thread: ela pode perder todas as corridas.

03

Wait-free: todos terminam

Uma operação wait-free tem um limite para seus próprios turnos que nenhum vizinho pode quebrar. worst_own_turns o mede: o maior número de turnos que uma thread toma em todos os escalonamentos completos. classify faz então a pergunta decisiva. O pior caso da thread 0 continua o mesmo quando o vizinho tem 1 incremento e quando tem 3? Se sim, a thread 0 é wait-free. Se cresce, o vizinho pode fazer a thread 0 tentar mais vezes, então a estrutura é apenas 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']
Qual escolher

Wait-free é o mais forte e o mais caro de projetar, e fetch-and-add mostra que nem sempre está fora de alcance. Lock-free é o alvo prático para filas e pilhas. Uma trava serve onde um detentor pausado não pode prejudicar, mas nunca num caminho que não pode travar.

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
O que dizem os dados reaisos dias de alta dos primeiros 6 retornos do SPY: 1 incremento para a thread 0, 3 para a thread 1
Trava giratóriablockinguma thread pausada para a outra
Compara-e-trocalock-freepior caso da thread 0: 8 turnos
Fetch-and-addwait-freepior caso da thread 0: 1 turno
Pior número de turnos próprios da thread 0 conforme o trabalho vizinho
Fetch-and-add, 3 incrementos vizinhos1
CAS, 1 incremento vizinho4
CAS, 2 incrementos vizinhos6
CAS, 3 incrementos vizinhos8

Leia as barras: a thread fetch-and-add precisa de 1 turno por mais ocupado que esteja o vizinho, enquanto a thread com compara-e-troca precisa de 4, 6 e depois 8 turnos conforme o vizinho faz mais incrementos. Cada incremento extra ao lado pode fazer a thread 0 perder mais uma corrida e repetir a leitura e a troca. Esse crescimento é exatamente o que separa lock-free de wait-free.

DefiniçãoGarantias de progresso

Bloqueante: uma thread pausada pode parar as outras. Lock-free: alguma thread sempre termina. Wait-free: cada thread termina em um número limitado de seus próprios turnos. Cada nível implica o anterior desta lista.

04

O que você vai construir

O laboratório segue a aula: o contador com trava giratória, a execução com algumas threads pausadas, o teste que diz se pausar uma thread pode parar as outras, o pior caso dos turnos próprios de uma thread, a classificação de um contador e o estudo dos três contadores nos dias de alta do SPY.

  • Função · perguntaBaseia-se em
  • locked_increments(k)Um contador que espera uma trava?Aritmética simples
  • progress_without(make_threads, prefix, paused, budget)Quem termina se algumas threads estão pausadas?run_prefix
  • is_blocking(make_threads, depth, budget)Uma thread pausada pode parar todo mundo?progress_without
  • worst_own_turns(make_threads, tid)De quantos turnos uma thread pode precisar?run_prefix
  • classify(factory, others)Bloqueante, lock-free ou wait-free?is_blockingworst_own_turns
  • study(bars)Onde ficam os três contadores no SPY?classifyis_blockingworst_own_turns

Pontos-chave

  • Uma trava pode parar todo mundo. Pause o detentor após um turno e a outra thread não termina nada em cinquenta rodadas.
  • Lock-free significa que alguém sempre termina. Mas uma thread pode perder todas as corridas: compara-e-troca precisa de 4, 6, 8 turnos quando o vizinho trabalha mais.
  • Wait-free tem um limite que nenhum vizinho quebra. Fetch-and-add termina em 1 turno faça o que fizer o vizinho.
Verificação rápida
Uma thread faz um incremento com compara-e-troca enquanto outra faz 5 incrementos. Qual afirmação é garantida?
Escolha uma resposta.

Próximo tópico: agora você sabe o que uma fila poderia prometer. Um buffer circular de um produtor e um consumidor constrói a fila sem travas útil mais simples, com um escritor e um leitor, memória fixa e nenhuma leitura-modificação-escrita atômica, e o explorador verifica que ela nunca perde nem reordena um tick.

Lab: o que é avaliado

6 tarefas · 100 pts
  • Um contador numa trava giratória10 pts
  • Executar com threads pausadas20 pts
  • Uma pausa pode parar todo mundo20 pts
  • O pior caso de uma thread15 pts
  • Nomear a garantia10 pts
  • O estudo com a tabela do SPY25 pts
Explorador · lab-lf-05
▾ lab-lf-05
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-lf-05$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

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