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

Um buffer circular de um produtor e um consumidor

Posição no roteiroMódulo 3 de 4 · Tópico 1 de 3
3.1 Um buffer circular de um produtor e um consumidor
—Nota do lab— / 100XP total—Sequência—

5 min de leitura4 exemplos de código

  1. 01Um círculo com um único escritor por posição
  2. 02Verificar os invariantes sobre todos os escalonamentos
  3. 03O que o quebra: um segundo produtor
  4. 04O que você vai construir

A fila sem travas mais simples não precisa de compara-e-troca nenhuma. Dê a cada posição um único escritor: só o produtor escreve a cauda, só o consumidor escreve a cabeça. Uma leitura obsoleta da posição do outro lado é sempre segura, então um buffer circular pode passar ticks entre duas threads sem trava. O explorador prova isso em 291 escalonamentos e mostra a quebra em 458 de 1072 quando entra um segundo produtor.

01

Um círculo com um único escritor por posição

Um buffer circular mantém dois contadores que só crescem. O produtor escreve a casa tail % capacity e depois publica subindo tail; o consumidor lê a casa head % capacity e depois a libera subindo head. O buffer está cheio quando tail - head == capacity e vazio quando head == tail. Por que é seguro sem trava? O produtor é o único escritor de tail, então pode guardar uma cópia local. Ele lê head para procurar espaço e, se o valor está obsoleto, a cabeça obsoleta é menor que a real, então o produtor acha o buffer mais cheio do que está e, no máximo, descarta um item, sem nunca sobrescrever uma casa não lida. O tail obsoleto do consumidor também é menor, então no máximo ele vê o buffer vazio. Este é o princípio do escritor único.

main.pyPython
def initial_state():    return {"head": 0, "tail": 0, "pushed": (), "popped": ()}def ring_producer(items, capacity):    def thread(shared):        tail = 0        for item in items:            yield            head = shared.get("head", 0)            if tail - head < capacity:                yield                shared["slot%d" % (tail % capacity)] = item                yield                tail += 1                shared["tail"] = tail                shared["pushed"] = shared.get("pushed", ()) + (item,)    return threaddef ring_consumer(attempts, capacity):    def thread(shared):        head = 0        for _ in range(attempts):            yield            tail = shared.get("tail", 0)            if head != tail:                yield                item = shared.get("slot%d" % (head % capacity))                yield                head += 1                shared["head"] = head                shared["popped"] = shared.get("popped", ()) + (item,)    return threadtwo = lambda shared: [ring_producer((7, 8), 2)(shared), ring_consumer(2, 2)(shared)]for prefix in ([0, 0, 0], [0, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]):    print(prefix, run_prefix(two, prefix, initial_state())[0])
[0, 0, 0] {'head': 0, 'tail': 1, 'pushed': (7,), 'popped': (), 'slot0': 7}
[0, 0, 0, 1, 1, 1] {'head': 1, 'tail': 1, 'pushed': (7,), 'popped': (7,), 'slot0': 7}
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1] {'head': 1, 'tail': 2, 'pushed': (7, 8), 'popped': (7,), 'slot0': 7, 'slot1': 8}

Três turnos do produtor empurram o primeiro tick: ele lê a cabeça, guarda o item em slot0 e publica tail: 1. Depois de três turnos do consumidor, head: 1 e popped: (7,). Na última linha o produtor empurrou os dois itens (slot0: 7, slot1: 8) e o consumidor pegou o primeiro; a thread 1 ainda está viva porque lhe resta uma tentativa.

02

Verificar os invariantes sobre todos os escalonamentos

O que uma fila deve prometer? Duas coisas. Os itens retirados são os itens inseridos, na mesma ordem e sem lacunas: popped é um prefixo de pushed (a propriedade FIFO). E nada é retirado que nunca foi inserido: len(popped) <= len(pushed). As threads registram o que inserem e retiram no mesmo turno que termina a operação, então violations pode verificar ambas em qualquer estado final. verify executa então o programa todo sob cada escalonamento com explore_all e conta os escalonamentos que quebram cada invariante. Um dicionário broken vazio sobre todos os escalonamentos é uma prova, para aquele tamanho de programa.

main.pyPython
def violations(final):    pushed, popped = tuple(final.get("pushed", ())), tuple(final.get("popped", ()))    broken = []    if popped != pushed[: len(popped)]:        broken.append("order")    if len(popped) > len(pushed):        broken.append("count")    return sorted(broken)def verify(producers, attempts, capacity):    def make_threads(shared):        return [ring_producer(items, capacity)(shared) for items in producers] + [ring_consumer(attempts, capacity)(shared)]    outcomes = explore_all(make_threads, initial_state())    broken = {}    pushed = popped = 0    for key, count in outcomes.items():        final = dict(key)        pushed = max(pushed, len(final.get("pushed", ())))        popped = max(popped, len(final.get("popped", ())))        for name in violations(final):            broken[name] = broken.get(name, 0) + count    return {"schedules": sum(outcomes.values()), "broken": dict(sorted(broken.items())), "pushed": pushed, "popped": popped}print(violations({"pushed": (7, 8), "popped": (8,)}))print(verify([(7, 8)], 2, 2))print(verify([(7, 8)], 2, 1))
['order']
{'schedules': 101, 'broken': {}, 'pushed': 2, 'popped': 2}
{'schedules': 25, 'broken': {}, 'pushed': 2, 'popped': 2}

Um produtor com dois itens num buffer de duas casas, lido por um consumidor com duas tentativas, tem 101 escalonamentos e nenhum quebra um invariante. Com um buffer de uma casa só o produtor precisa descartar um item quando o consumidor é lento, mas continua correto: 25 escalonamentos, nenhuma violação. A lista retirada (8,) depois de (7, 8) inserido é, como esperado, uma violação da ordem.

DefiniçãoPrincípio do escritor único

Cada variável compartilhada tem exatamente uma thread que a escreve, e qualquer número que só a lê. Com um único escritor não há corrida nessa variável, e um leitor que vê um valor antigo continua seguro se o valor antigo só erra para o lado seguro.

03

O que o quebra: um segundo produtor

O argumento de segurança se apoia num fato: tail tem um escritor. Acrescente um segundo produtor e duas threads mantêm cada uma sua ideia da cauda. Ambas leem o mesmo head, calculam a mesma casa e a segunda escrita sobrescreve a primeira. O explorador acha: 458 dos 1072 escalonamentos de dois produtores (um tick cada) terminam com um popped que não é prefixo de pushed. A solução não é um remendo neste buffer, e sim outra estrutura, uma fila de vários produtores, que os próximos tópicos constroem com compara-e-troca.

main.pyPython
print(verify([(7,), (8,)], 2, 2))
{'schedules': 1072, 'broken': {'order': 458}, 'pushed': 2, 'popped': 1}
Em hardware real a ordem das duas escritas importa

Esta máquina executa cada operação em ordem. Num processador real o produtor deve publicar tail com uma escrita release e o consumidor lê-lo com um carregamento acquire, senão o consumidor pode ver a cauda nova antes do item (os dados obsoletos do tópico anterior). Ponha também head e tail em linhas de cache diferentes, senão as duas threads disputam uma linha (falso compartilhamento).

main.pyPython
ticks = [int(round(float(c) * 100)) for c in load_bars()["close"].iloc[:4]]print(ticks)print("one producer: ", verify([ticks[:2]], 3, 2))print("two producers:", verify([ticks[:1], ticks[1:2]], 2, 2))
[9369, 9305, 9354, 9349]
one producer:  {'schedules': 291, 'broken': {}, 'pushed': 2, 'popped': 2}
two producers: {'schedules': 1072, 'broken': {'order': 458}, 'pushed': 2, 'popped': 1}
O que dizem os dados reaisos primeiros quatro fechamentos do SPY (9369, 9305, 9354 e 9349 centavos) empurrados por um buffer
Um produtor291escalonamentos, 0 quebram um invariante
Dois produtores1,072escalonamentos explorados
Ordem quebrada458dos escalonamentos de dois produtores
Parcela quebrada43%dos escalonamentos de dois produtores
Escalonamentos por veredito
Um produtor: todos certos291
Dois produtores: certos614
Dois produtores: ordem quebrada458

A lição inteira cabe em dois números: um escritor por posição basta para a correção em 291 escalonamentos, e dois escritores na mesma posição erram em 43% de 1072. O explorador não adivinhou; executou todas as intercalações.

04

O que você vai construir

O laboratório segue a aula: o buffer vazio, as threads produtora e consumidora, a verificação dos dois invariantes, o verificador que roda o explorador e o estudo que envia fechamentos do SPY pelo buffer com um e com dois produtores.

  • Função · perguntaBaseia-se em
  • initial_state()Como é um buffer vazio?Aritmética simples
  • ring_producer(items, capacity)Como o produtor empurra?Aritmética simples
  • ring_consumer(attempts, capacity)Como o consumidor retira?Aritmética simples
  • violations(final)Quais invariantes este estado quebra?Aritmética simples
  • verify(producers, attempts, capacity)O buffer está certo sob todos os escalonamentos?explore_allviolations
  • study(bars)O que quebra com um segundo produtor?verify

Pontos-chave

  • Um escritor por posição. Só o produtor escreve a cauda e só o consumidor a cabeça, então não é preciso compara-e-troca.
  • Uma leitura obsoleta erra para o lado seguro. Uma cabeça obsoleta faz o buffer parecer mais cheio e uma cauda obsoleta mais vazio: no máximo um item é descartado ou uma retirada não encontra nada.
  • Quebre o contrato e o explorador acha. Dois produtores numa única cauda quebram a ordem em 458 de 1072 escalonamentos.
Verificação rápida
Um buffer tem 4 casas. O produtor empurrou 9 itens no total e o consumidor retirou 6. Quantos itens há no buffer e em qual casa será guardado o próximo?
Escolha uma resposta.

Próximo tópico: o truque do escritor único tem um limite, e o compara-e-troca também. O problema ABA e os ponteiros etiquetados constrói uma pilha sem travas sobre compara-e-troca, mostra um escalonamento em que a troca tem sucesso embora a pilha tenha mudado por baixo, e a corrige com uma etiqueta de versão.

Lab: o que é avaliado

6 tarefas · 100 pts
  • O buffer vazio5 pts
  • O produtor20 pts
  • O consumidor20 pts
  • Os invariantes10 pts
  • Verificar sobre todos os escalonamentos15 pts
  • O estudo com a tabela do SPY30 pts
Explorador · lab-lf-06
▾ lab-lf-06
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-lf-06$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

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