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

Filas de vários produtores e vários consumidores

Posição no roteiroMódulo 3 de 4 · Tópico 3 de 3
3.3 Filas de vários produtores e vários consumidores
—Nota do lab— / 100XP total—Sequência—

5 min de leitura4 exemplos de código

  1. 01Um tíquete não é uma entrega
  2. 02Verificar todos os escalonamentos
  3. 03O que o número de sequência traz
  4. 04O que você vai construir

Com um produtor e um consumidor uma posição tem um escritor. Com muitos, as posições precisam ser distribuídas atomicamente, e um tíquete não basta: o produtor que tem a posição 5 ainda não escreveu seu item. Um número de sequência em cada casa diz de quem é a vez da casa. Em 2756 escalonamentos de dois produtores e 1600 de dois consumidores, essa fila nunca quebra.

01

Um tíquete não é uma entrega

Uma fila limitada de capacity casas usa dois contadores que só crescem. Um produtor toma a próxima posição com um compara-e-troca em enq; um consumidor toma a próxima com um em deq. A posição p vive na casa p % capacity. O compara-e-troca torna as reservas seguras: dois produtores nunca recebem a mesma posição. Mas reservar uma posição não é escrever o item. Entre a troca e a escrita a casa está reservada e vazia, e um consumidor que a lesse agora leria lixo. Cada casa carrega por isso um número de sequência que só avança no momento certo.

main.pyPython
def init_queue(capacity):    shared = {"enq": 0, "deq": 0}    for cell in range(capacity):        shared["seq%d" % cell] = cell    return shareddef enqueue(shared, item, capacity):    while True:        yield        pos = shared["enq"]        cell = pos % capacity        diff = shared["seq%d" % cell] - pos        if diff < 0:            return False        if diff > 0:            continue        yield        if compare_and_swap(shared, "enq", pos, pos + 1):            break    yield    shared["data%d" % cell] = item    shared["seq%d" % cell] = pos + 1    shared["put%d" % pos] = item    return Truedef dequeue(shared, capacity):    while True:        yield        pos = shared["deq"]        cell = pos % capacity        diff = shared["seq%d" % cell] - (pos + 1)        if diff < 0:            return None        if diff > 0:            continue        yield        if compare_and_swap(shared, "deq", pos, pos + 1):            break    yield    item = shared.get("data%d" % cell)    shared["seq%d" % cell] = pos + capacity    shared["got%d" % pos] = item    return itemsolo = lambda shared: [producer_thread((7,), 2)(shared), consumer_thread(1, 2)(shared)]for prefix in ([0, 0, 0], [0, 0, 0, 1, 1, 1], [0, 0, 1, 1, 0, 1, 1]):    print(prefix, run_prefix(solo, prefix, init_queue(2))[0])
[0, 0, 0] {'enq': 1, 'deq': 0, 'seq0': 1, 'seq1': 1, 'data0': 7, 'put0': 7}
[0, 0, 0, 1, 1, 1] {'enq': 1, 'deq': 1, 'seq0': 2, 'seq1': 1, 'data0': 7, 'put0': 7, 'got0': 7}
[0, 0, 1, 1, 0, 1, 1] {'enq': 1, 'deq': 0, 'seq0': 1, 'seq1': 1, 'data0': 7, 'put0': 7}

Leia as três execuções. Depois de [0, 0, 0] o produtor reservou a posição 0 (enq: 1), guardou data0 e subiu seq0 para 1: o item está pronto. Depois dos três turnos do consumidor, got0: 7 e seq0: 2: a casa fica livre para a próxima volta. Na última execução o consumidor olha entre a reserva do produtor e sua publicação, vê seq0 = 0 em vez de 1 e vai embora de mãos vazias. Ele não lê lixo. Só diz «nada pronto ainda», e o item será achado por uma tentativa posterior.

02

Verificar todos os escalonamentos

O que a fila deve prometer agora? Os produtores registram cada item sob sua posição (put3), os consumidores registram o que pegaram sob a posição que reservaram (got3). Se a fila funciona, cada posição retirada contém o item que foi posto ali (sem mismatch), e as posições retiradas são 0, 1, 2, ... sem buracos nem repetições (sem gap). Isso dá à fila sua ordem: a ordem das posições. violations lê esses dois fatos de um estado final e verify roda o explorador para qualquer número de produtores e consumidores. Aqui, um único produtor com dois itens contra dois consumidores, e um único item contra um consumidor.

main.pyPython
def violations(final):    puts = {int(key[3:]): value for key, value in final.items() if key.startswith("put")}    gots = {int(key[3:]): value for key, value in final.items() if key.startswith("got")}    broken = []    if any(gots[p] != puts.get(p) for p in gots):        broken.append("mismatch")    if sorted(gots) != list(range(len(gots))):        broken.append("gap")    return sorted(broken)def verify(producers, consumers, capacity):    def make_threads(shared):        return [producer_thread(items, capacity)(shared) for items in producers] + [consumer_thread(n, capacity)(shared) for n in consumers]    outcomes = explore_all(make_threads, init_queue(capacity))    broken = {}    pushed = popped = 0    for key, count in outcomes.items():        final = dict(key)        pushed = max(pushed, sum(1 for k in final if k.startswith("put")))        popped = max(popped, 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())), "pushed": pushed, "popped": popped}print(verify([(7,)], [1], 2))print(verify([(7, 8)], [1, 1], 2))
{'schedules': 4, 'broken': {}, 'pushed': 1, 'popped': 1}
{'schedules': 1600, 'broken': {}, 'pushed': 2, 'popped': 2}
DefiniçãoNúmero de sequência

Um contador em cada casa que registra de quem é a vez. Só muda quando o item é escrito (para p + 1) e quando é retirado (para p + capacity), então uma thread que o lê sabe se o item é seguro de usar sem nenhuma trava.

03

O que o número de sequência traz

Para ver o valor, tire-o. O consumidor descuidado abaixo reserva uma posição com o compara-e-troca e depois lê a casa na hora, sem olhar seq. Contra um produtor de um só item ele tem 20 escalonamentos, e em 10 deles reserva uma posição antes de o produtor publicar qualquer coisa e devolve None como item, ou o item errado. A fila real tem 4 escalonamentos para o mesmo programa e nenhum quebra. O número de sequência é exatamente a verificação que transforma um tíquete reservado num item entregue.

main.pyPython
def careless_dequeue(shared, capacity):    yield    pos = shared["deq"]    cell = pos % capacity    yield    if not compare_and_swap(shared, "deq", pos, pos + 1):        return None    yield    item = shared.get("data%d" % cell)    shared["got%d" % pos] = item    return itemdef careless_consumer(attempts, capacity):    def thread(shared):        for _ in range(attempts):            yield from careless_dequeue(shared, capacity)    return threadmake = lambda shared: [producer_thread((7,), 2)(shared), careless_consumer(1, 2)(shared)]outcomes = explore_all(make, init_queue(2))broken = sum(n for key, n in outcomes.items() if violations(dict(key)))print(sum(outcomes.values()), "schedules,", broken, "break the invariants")
20 schedules, 10 break the invariants
A capacidade deve ser pelo menos dois

Com uma única casa, o valor p + 1 que significa «pronta para a posição p» é também o que significa «livre para a posição p + 1», e um produtor poderia sobrescrever um item não lido. Por isso as implementações reais arredondam a capacidade para uma potência de dois, pelo menos 2, o que também transforma % capacity numa máscara barata.

main.pyPython
ticks = [int(round(float(c) * 100)) for c in load_bars()["close"].iloc[:2]]print(ticks)print("two producers:", verify([(ticks[0],), (ticks[1],)], [2], 2))print("two consumers:", verify([tuple(ticks)], [1, 1], 2))
[9369, 9305]
two producers: {'schedules': 2756, 'broken': {}, 'pushed': 2, 'popped': 2}
two consumers: {'schedules': 1600, 'broken': {}, 'pushed': 2, 'popped': 2}
O que dizem os dados reaisos dois primeiros fechamentos do SPY (9369 e 9305 centavos) por uma fila de duas casas
Dois produtores, um consumidor2,756escalonamentos, 0 quebram um invariante
Um produtor, dois consumidores1,600escalonamentos, 0 quebram um invariante
Consumidor descuidado10dos 20 escalonamentos leem um item ausente

As duas formas da fila estão corretas sobre todos os escalonamentos, com o explorador como testemunha, enquanto uma versão sem a verificação de sequência falha na metade do seu espaço (bem menor). Produtores e consumidores nunca esperam uns pelos outros: um consumidor que não encontra nada pronto simplesmente volta, e um produtor que encontra a fila cheia devolve False. Esse é o preço e o presente de uma fila sem travas: as operações podem falhar, mas nunca bloqueiam.

04

O que você vai construir

O laboratório segue a aula: a fila vazia com seus números de sequência, o enqueue e o dequeue com reserva por compara-e-troca e publicação, a verificação dos dois invariantes, o verificador que roda o explorador para qualquer número de produtores e consumidores, e o estudo sobre dois fechamentos do SPY.

  • Função · perguntaBaseia-se em
  • init_queue(capacity)Como é uma fila vazia?Aritmética simples
  • enqueue(shared, item, capacity)Como um produtor reserva e publica?Aritmética simples
  • dequeue(shared, capacity)Como um consumidor reserva e libera?Aritmética simples
  • violations(final)Quais invariantes um estado final quebra?Aritmética simples
  • verify(producers, consumers, capacity)A fila está certa sob todos os escalonamentos?explore_allviolations
  • study(bars)As duas formas se sustentam com fechamentos do SPY?verify

Pontos-chave

  • Tíquetes com compara-e-troca, prontidão com números de sequência. Reservar uma posição a torna sua; o número de sequência diz a todos quando o item nela está pronto.
  • Não pronto não é um erro. Um consumidor que não encontra nada pronto devolve None e tenta depois; nunca espera e nunca lê lixo.
  • Prove, tirando o número de sequência como controle. 1600 e 2756 escalonamentos sem violação; o consumidor descuidado quebra em 10 de 20.
Verificação rápida
Uma fila tem 4 casas. Um consumidor reservou a posição 6 e liberou sua casa. Qual é agora o número de sequência dessa casa e para qual posição ela está livre?
Escolha uma resposta.

Próximo tópico: você agora provou a correção de programas pequenos executando todos os escalonamentos. Testar código concorrente compara isso com as ferramentas que as equipes reais usam: um detector de corridas que observa uma execução e sinaliza os acessos que poderiam competir, e um verificador de modelos que faz o que o seu explorador faz, mas com os truques que o deixam escalar.

Lab: o que é avaliado

6 tarefas · 100 pts
  • A fila vazia5 pts
  • Enqueue25 pts
  • Dequeue25 pts
  • Os invariantes10 pts
  • Verificar sobre todos os escalonamentos15 pts
  • O estudo com a tabela do SPY20 pts
Explorador · lab-lf-08
▾ lab-lf-08
▾ data
…
bash · no navegador
learner@quantcraft:~/lab-lf-08$
Python ainda não carregadoPythonLn 1, Col 1Spaces: 4UTF-8

Redefinir este tópico?

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