σμ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.3·~2 hEspecialista

Projeto final: uma distribuição de dados de mercado sem travas com provas de seus invariantes

Posição no roteiroMódulo 4 de 4 · Tópico 3 de 3
4.3 Projeto final: uma distribuição de dados de mercado sem travas com provas de seus invariantes
—Nota do lab— / 100XP total—Sequência—

4 min de leitura3 exemplos de código

  1. 01O desenho: um registro e um cursor cada
  2. 02O consumidor: ler a cauda e depois o cursor
  3. 03Três invariantes e depois cada escalonamento
  4. 04O que você vai construir

O projeto final é uma distribuição: um produtor publica ticks do SPY e vários consumidores leem todos eles, sem nenhuma trava. Tudo o que você construiu se encontra aqui. Uma cauda que só o produtor move, um cursor privado por consumidor para que ninguém dispute, uma ordem de escrita que decide o que um leitor pode ver e um explorador que transforma «parece funcionar» numa afirmação sobre cada escalonamento.

01

O desenho: um registro e um cursor cada

O fluxo é um registro que só cresce. A posição p guarda tick<p> e tail diz quantas posições foram publicadas. Só o produtor escreve ticks e a cauda, então não há o que disputar do lado dele. Cada consumidor possui um cursor, a próxima posição que lerá, e ninguém mais o escreve. Esse é todo o truque de uma distribuição: os consumidores nunca escrevem nada que os outros leem, então acrescentar um consumidor não custa nada aos demais. O único fato compartilhado é a cauda, e a única pergunta é em que ordem o produtor escreve o tick e move a cauda. Movê-la depois dos dados: um leitor que vê a nova cauda encontra os dados. Movê-la antes: um leitor pode ver uma posição ainda não escrita.

main.pyPython
def publish(shared, tick, safe=True):    if safe:        yield        pos = shared["tail"]        shared["tick%d" % pos] = tick        shared["put%d" % pos] = tick        yield        shared["tail"] = pos + 1    else:        yield        pos = shared["tail"]        shared["tail"] = pos + 1        yield        shared["tick%d" % pos] = tick        shared["put%d" % pos] = tickfor safe in (True, False):    shared, _ = run_prefix(lambda sh: [publish(sh, 9369, safe)], [0], make_feed(1))    print("safe  " if safe else "unsafe", "after one turn:", {k: v for k, v in shared.items() if k in ("tail", "tick0")})
safe   after one turn: {'tail': 0, 'tick0': 9369}
unsafe after one turn: {'tail': 1}
02

O consumidor: ler a cauda e depois o cursor

Uma consulta leva dois turnos. O primeiro lê a cauda. O segundo lê o cursor do consumidor e, se o cursor está atrás da cauda, lê o tick, avança o cursor e anota o que obteve. Nada pode mudar a posição sob seus pés, porque só este consumidor escreve seu cursor. O que pode dar errado é o conteúdo do tick: um leitor que entra depois de a cauda mover mas antes de os dados chegarem não lê nada. A consulta anota isso como None, para o verificador ver.

main.pyPython
def poll(shared, c):    yield    tail = shared["tail"]    yield    pos = shared["cur%d" % c]    if pos >= tail:        return None    value = shared.get("tick%d" % pos)    shared["cur%d" % c] = pos + 1    shared["got%d_%d" % (c, pos)] = value    return valueready = dict(make_feed(1), tail=1)shared, alive = run_prefix(lambda sh: [poll(sh, 0)], [0, 0], ready)print("tail is 1 but tick0 was never written:", {k: v for k, v in shared.items() if k.startswith(("cur", "got"))})
tail is 1 but tick0 was never written: {'cur0': 1, 'got0_0': None}
Por que um consumidor não pode simplesmente esperar

Uma consulta que não acha nada devolve None na hora em vez de girar até o tick aparecer. Isso mantém finito o programa de cada thread, o que permite ao explorador terminar, e é também o contrato sem travas: nenhuma thread depende de outra progredir para completar a própria chamada.

03

Três invariantes e depois cada escalonamento

O que significa «correto» para o fluxo? Três afirmações sobre o estado final. Rasgado: cada tick recebido por um consumidor é igual ao tick posto naquela posição (um tick ausente recebido como None está rasgado). Buraco: cada consumidor recebeu as posições 0, 1, 2, ... sem buraco. Estouro: nenhum cursor está além da cauda. verify explora cada escalonamento de um produtor e dos consumidores e conta os que quebram cada uma. O explorador é a prova: não pode amostrar a execução sortuda, visita todas.

main.pyPython
def violations(final):    puts = {int(key[3:]): value for key, value in final.items() if key.startswith("put")}    gots = {}    for key, value in final.items():        if key.startswith("got"):            c, p = key[3:].split("_")            gots[(int(c), int(p))] = value    broken = []    if any(value != puts.get(p) for (c, p), value in gots.items()):        broken.append("torn")    for c in {c for c, _ in gots}:        if sorted(p for cc, p in gots if cc == c) != list(range(sum(1 for cc, _ in gots if cc == c))):            broken.append("gap")            break    if any(key.startswith("cur") and value > final["tail"] for key, value in final.items()):        broken.append("overrun")    return sorted(broken)def verify(ticks, polls, safe=True):    outcomes = explore_all(feed_threads(ticks, polls, safe), make_feed(len(polls)))    broken = {}    delivered = 0    for key, count in outcomes.items():        final = dict(key)        delivered = max(delivered, sum(1 for k in final if k.startswith("got")))        for name in violations(final):            broken[name] = broken.get(name, 0) + count    return {"schedules": sum(outcomes.values()), "broken": dict(sorted(broken.items())), "delivered": delivered}ticks = [9369, 9305]print("safe  ", verify(ticks, (2, 1), True))print("unsafe", verify(ticks, (2, 1), False))
safe   {'schedules': 3150, 'broken': {}, 'delivered': 3}
unsafe {'schedules': 3150, 'broken': {'torn': 869}, 'delivered': 3}
O que dizem os dados reaisum produtor com os dois primeiros fechamentos do SPY (9369 e 9305 centavos), um consumidor consultando duas vezes e outro uma
Escalonamentos3,150explorados para cada ordem
Dados primeiro0escalonamentos quebram um invariante
Cauda primeiro869escalonamentos entregam um tick que não está lá
Uma execução calma2ticks recebidos por cada consumidor, em ordem

O mesmo programa, 3150 escalonamentos, e uma única troca de ordem de duas linhas transforma «nenhum quebrado» em 869 quebrados. A maioria são escalonamentos que alguns milhares de execuções de teste quase nunca atingiriam. Esse é o argumento para explorar em vez de testar, e é toda a habilidade numa tabela: um modelo pequeno o bastante para enumerar, um verificador de invariantes e um veredicto sobre cada escalonamento.

04

O que você vai construir

O laboratório segue a aula: o fluxo vazio, o produtor nas suas duas ordens, a consulta do consumidor, o verificador dos três invariantes, o verificador que explora todos os escalonamentos, uma repetição de uma execução calma em que cada consumidor recebe os ticks em ordem e o estudo em fechamentos do SPY.

  • Função · perguntaBaseia-se em
  • make_feed(consumers)O que é compartilhado?Aritmética simples
  • publish(shared, tick, safe)Dados primeiro ou cauda primeiroAritmética simples
  • poll(shared, c)O próximo tick do consumidor cAritmética simples
  • violations(final)Quais invariantes quebraram?Aritmética simples
  • verify(ticks, polls, safe)O fluxo está correto sob todos os escalonamentos?explore_allviolations
  • drain(ticks, polls)O que cada consumidor recebe?run_prefix
  • study(bars)Seguro, inseguro e calmo no SPYverifydrain

Pontos-chave

  • A distribuição escala porque leitores nunca escrevem o que outros leem. Um cursor privado por consumidor: um novo consumidor não custa nada aos outros.
  • A ordem de escrita é o contrato. Dados antes da cauda é seguro; a cauda antes dos dados quebra 869 de 3150 escalonamentos.
  • Prove sobre cada escalonamento. Enuncie os invariantes, explore todo o modelo e leia a contagem de escalonamentos quebrados.
Verificação rápida
Na ordem insegura o produtor move a cauda primeiro. Qual leitor vê um tick rasgado?
Escolha uma resposta.

Isso completa a habilidade sem travas. Agora você sabe construir as peças (atômicos, compare-and-swap, pilhas e filas), nomear os perigos (corridas, ABA, reclamação) e, acima de tudo, checar um desenho contra cada escalonamento em vez de confiar numa execução que passou por acaso. O próximo passo é a mesma disciplina em hardware real, nos tópicos de atômicos de C++.

Lab: o que é avaliado

7 tarefas · 100 pts
  • O fluxo vazio5 pts
  • O produtor15 pts
  • O consumidor20 pts
  • Os invariantes15 pts
  • O verificador15 pts
  • Uma execução calma10 pts
  • O estudo com a tabela do SPY20 pts
Explorador · lab-lf-11
▾ lab-lf-11
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-lf-11$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

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