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

O modelo de memória: acquire e release

Posição no roteiroMódulo 2 de 4 · Tópico 2 de 3
2.2 O modelo de memória: acquire e release
—Nota do lab— / 100XP total—Sequência—

5 min de leitura4 exemplos de código

  1. 01Processadores reordenam
  2. 02Executar todas as ordens
  3. 03Release e acquire andam em par
  4. 04O que você vai construir

Um processador é livre para executar duas leituras e escritas independentes em qualquer ordem, e outro núcleo pode ver o resultado dessa escolha. Publique um preço do SPY e depois levante uma flag: um leitor que vê a flag ainda pode ler um preço de 0. Das quatro maneiras de ordenar o escritor e o leitor, só uma é segura: release no escritor pareado com acquire no leitor.

01

Processadores reordenam

Buffers de escrita, caches e o compilador deixam um núcleo terminar uma escrita ou leitura posterior antes de uma anterior, desde que uma única thread não perceba. Outra thread percebe. Nossa máquina modela isso diretamente: uma thread é uma lista de operações e pode executar, em qualquer ordem, toda operação cujas restrições de ordem estejam satisfeitas. Operações no mesmo endereço sempre mantêm a ordem, senão uma thread não veria suas próprias escritas. Um carregamento acquire bloqueia tudo o que vem depois; uma escrita release espera tudo o que vem antes. Duas funções dizem tudo: must_wait lista o que uma operação espera e eligible lista o que pode executar a seguir.

main.pyPython
def must_wait(ops, j):    waits = []    for i in range(j):        same = ops[i][1] == ops[j][1]        after_acquire = ops[i][0] == "load" and ops[i][3] == "acquire"        release = ops[j][0] == "store" and ops[j][3] == "release"        if same or after_acquire or release:            waits.append(i)    return waitsdef eligible(ops, done):    return [j for j in range(len(ops)) if j not in done and all(i in done for i in must_wait(ops, j))]writer = [("store", "data", 1, "relaxed"), ("store", "flag", 1, "release")]reader = [("load", "flag", "r1", "acquire"), ("load", "data", "r2", "relaxed")]print(must_wait(writer, 1), eligible(writer, set()), eligible(writer, {0}))print(must_wait(reader, 1), eligible(reader, set()))print(eligible([("load", "flag", "r1", "relaxed"), ("load", "data", "r2", "relaxed")], set()))
[0] [0] [1]
[0] [0]
[0, 1]

A escrita release da flag espera a posição 0, a escrita dos dados, então o escritor só pode executar suas operações em ordem. O carregamento acquire da flag bloqueia o carregamento dos dados que vem depois, então o leitor também executa em ordem. Com carregamentos simples, ambos são elegíveis ao mesmo tempo e o leitor pode ler os dados primeiro.

02

Executar todas as ordens

outcomes é o explorador dos tópicos anteriores para esta nova máquina. Um estado é quais operações cada thread já fez, a memória e os registradores. A partir de um estado, tenta cada operação elegível de cada thread, e quando não resta nada a executar os registradores são um resultado. Como muitas ordens chegam ao mesmo estado, ele lembra dos estados já vistos. O teste de passagem de mensagem publica com duas escritas, os dados e depois a flag, e consome com dois carregamentos, a flag e depois os dados. O consumidor erra quando vê r1 == 1 (a flag) e r2 == 0 (ainda sem dados), o que reads_stale verifica.

main.pyPython
def outcomes(programs, memory=None):    found = set()    seen = set()    def visit(done, mem, regs):        state = (tuple(frozenset(d) for d in done), tuple(sorted(mem.items())), tuple(sorted(regs.items())))        if state in seen:            return        seen.add(state)        moved = False        for tid, ops in enumerate(programs):            for j in eligible(ops, done[tid]):                moved = True                kind, address, x, _ = ops[j]                mem2, regs2 = dict(mem), dict(regs)                if kind == "store":                    mem2[address] = x                else:                    regs2[x] = mem.get(address, 0)                done2 = [set(d) for d in done]                done2[tid].add(j)                visit(done2, mem2, regs2)        if not moved:            found.add(tuple(sorted(regs.items())))    visit([set() for _ in programs], dict(memory or {}), {})    return founddef mp_programs(write_order, read_order):    return [        [("store", "data", 1, "relaxed"), ("store", "flag", 1, write_order)],        [("load", "flag", "r1", read_order), ("load", "data", "r2", "relaxed")],    ]def reads_stale(results):    return any(dict(outcome).get("r1") == 1 and dict(outcome).get("r2") == 0 for outcome in results)for pair in (("relaxed", "relaxed"), ("release", "acquire")):    results = outcomes(mp_programs(*pair))    print(pair, sorted(results), "stale:", reads_stale(results))
('relaxed', 'relaxed') [(('r1', 0), ('r2', 0)), (('r1', 0), ('r2', 1)), (('r1', 1), ('r2', 0)), (('r1', 1), ('r2', 1))] stale: True
('release', 'acquire') [(('r1', 0), ('r2', 0)), (('r1', 0), ('r2', 1)), (('r1', 1), ('r2', 1))] stale: False

Com ordens relaxed o consumidor pode terminar com os quatro pares (r1, r2), incluindo (1, 0), o obsoleto. Com release na escrita da flag e acquire no seu carregamento restam só três: o leitor que vê a flag vê também os dados, porque nenhum dos lados pode ser reordenado através da flag.

03

Release e acquire andam em par

Um lado só basta? Teste os quatro pares. Release só no escritor falha: o leitor pode carregar os dados antes da flag. Acquire só no leitor falha: o escritor pode guardar a flag antes dos dados. Só o par funciona, como mostra a tabela a seguir. É o contrato release-acquire do std::atomic do C++ e do Rust, e o motivo de uma fila sem travas publicar uma casa com uma escrita release de um índice e lê-la com um carregamento acquire.

main.pyPython
PAIRS = [("relaxed", "relaxed"), ("release", "relaxed"), ("relaxed", "acquire"), ("release", "acquire")]def ordering_table():    return {pair: reads_stale(outcomes(mp_programs(*pair))) for pair in PAIRS}for pair, stale in ordering_table().items():    print("write", pair[0], "| read", pair[1], "->", "can read stale data" if stale else "safe")
write relaxed | read relaxed -> can read stale data
write release | read relaxed -> can read stale data
write relaxed | read acquire -> can read stale data
write release | read acquire -> safe
Acquire e release são um par, não uma palavra mágica

Um release sem o acquire correspondente na mesma variável não protege nada. Uma ordem de memória real do C++ como seq_cst é mais forte e custa mais; relaxed é a mais barata e não promete ordem alguma. Escolha a ordem mais fraca que faça seu teste acima passar e prove com um explorador.

main.pyPython
price = int(round(float(load_bars()["close"].iloc[0]) * 100))programs = mp_programs("release", "acquire")programs[0][0] = ("store", "data", price, "relaxed")print(price, sorted(outcomes(programs)))
9369 [(('r1', 0), ('r2', 0)), (('r1', 0), ('r2', 9369)), (('r1', 1), ('r2', 9369))]
O que dizem os dados reaiso primeiro fechamento do SPY publicado como 9369 centavos por uma flag
Relaxed + relaxed4resultados, preço obsoleto possível
Só release4resultados, preço obsoleto possível
Só acquire4resultados, preço obsoleto possível
Release + acquire3resultados, nunca obsoleto

Com o primeiro fechamento do SPY, 9369 centavos, o leitor pode ver a flag e um preço de 0 em três das quatro configurações. Um bug desse tipo passa em todos os testes numa máquina que mantém a ordem e aparece em outra. Por isso o par seguro precisa ser provado sobre todas as ordens permitidas, como faz o explorador.

DefiniçãoPar release-acquire

Uma escrita release de uma flag e um carregamento acquire que a lê. Tudo o que o escritor fez antes do release é visível ao leitor depois do acquire. É a ordem mais barata que torna correta a publicação de dados por uma flag.

04

O que você vai construir

O laboratório segue a aula: o que uma operação deve esperar, quais operações podem executar a seguir, todos os resultados de um conjunto de threads, o teste de passagem de mensagem, a verificação de dados obsoletos, a tabela das quatro ordens e o estudo que publica um preço real do SPY.

  • Função · perguntaBaseia-se em
  • must_wait(ops, j)O que esta operação deve esperar?Aritmética simples
  • eligible(ops, done)Quais operações podem ir a seguir?Aritmética simples
  • outcomes(programs, memory)Com o que as threads podem terminar?eligible
  • mp_programs(write_order, read_order)Como é a passagem de mensagem?Aritmética simples
  • reads_stale(results)O leitor pode ver a flag sem os dados?Aritmética simples
  • ordering_table()Quais pares de ordens são seguros?outcomesmp_programs
  • study(bars)Quais pares protegem um preço real do SPY?outcomesmp_programs

Pontos-chave

  • A reordenação é permitida a menos que uma regra a proíba. Operações no mesmo endereço mantêm a ordem; carregamentos acquire e escritas release acrescentam as barreiras.
  • Publicar precisa de um par. Release na escrita da flag mais acquire no carregamento: 3 resultados e nenhum preço obsoleto; qualquer outro par tem 4 e pode ler 0.
  • Prove sobre todas as ordens. Um teste numa máquina pode passar por sorte; o explorador verifica todas as ordens que as regras permitem.
Verificação rápida
O escritor guarda os dados e depois faz uma escrita release da flag. O leitor faz um carregamento simples (relaxed) da flag e depois um carregamento simples dos dados. O leitor pode ver a flag levantada e os dados ausentes?
Escolha uma resposta.

Próximo tópico: agora você tem as ferramentas para construir uma estrutura sem travas e um vocabulário para descrever o que ela garante. Garantias de progresso nomeia os três níveis, obstruction-free, lock-free e wait-free, e testa cada contador que você escreveu contra uma thread pausada para sempre no pior momento.

Lab: o que é avaliado

7 tarefas · 100 pts
  • O que uma operação espera15 pts
  • Quais podem ir a seguir10 pts
  • Cada resultado25 pts
  • O teste de passagem de mensagem10 pts
  • Dados obsoletos5 pts
  • As quatro ordens10 pts
  • O estudo com a tabela do SPY25 pts
Explorador · lab-lf-04
▾ lab-lf-04
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-lf-04$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

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