Pular para o conteúdo

Concorrência

async/await, threads, canais e o que ainda não existe.

async / await#

dataforge
async action buscar_usuario(id):
    yield {"id": id, "nome": $"Usuario {id}"}

usuario := await buscar_usuario(7)
out usuario

thread#

dataforge
resultados := []

thread:
    cycle i from 1 to 3:
        resultados.append($"A{i}")

wait 200        # espera as threads
out len(resultados)

O bloco roda numa thread daemon: o programa não espera por ela. Se terminar antes, a thread é interrompida no meio.

A condição de corrida#

Este é o ponto mais importante desta página:

dataforge
contador := {"valor": 0}

thread:
    cycle _ in range(0, 1000):
        contador["valor"] := contador["valor"] + 1

thread:
    cycle _ in range(0, 1000):
        contador["valor"] := contador["valor"] + 1

wait 400
out contador["valor"]     # deveria ser 2000. Frequentemente é menos.

contador["valor"] + 1 são três passos — ler, somar, escrever. Se as duas threads leem 5 ao mesmo tempo, ambas escrevem 6. Um incremento se perdeu.

Rode várias vezes: o número muda. É o tipo de bug que passa em teste e quebra em produção sob carga.

channel — a via segura#

DataForge 4.0 não tem mutex nem lock. A solução não é sincronizar o acesso — é não compartilhar:

dataforge
channel parciais

thread:
    soma := 0                  # variável local desta thread
    cycle _ in range(0, 1000):
        soma += 1
    parciais.send(soma)        # reporta uma vez, no fim

thread:
    soma := 0
    cycle _ in range(0, 1000):
        soma += 1
    parciais.send(soma)

wait 400

total := 0
persist yes:
    parcial := parciais.receive()
    given parcial is void:
        halt
    total += parcial

out total     # 2000, sempre

Cada thread trabalha no próprio escopo. Ninguém escreve onde outro lê. Essa ideia tem nome — "não comunique compartilhando memória; compartilhe memória comunicando" — e é o lema de Go.

Regra prática#

SituaçãoSeguro?
threads só leem dados compartilhadossim
cada thread escreve numa variável própriasim
threads enviam por channelsim
duas threads escrevem na mesma variávelnão
lista.append de duas threadsnão

parallel#

dataforge
parallel:
    saidas.append(tarefa_a())
    saidas.append(tarefa_b())

Cada instrução do bloco vai para uma thread — não uma thread para o bloco inteiro. É uma limitação conhecida; enquanto isso, mantenha cada linha autossuficiente.

O que ainda não existe#

  • Mutex, Semaphore, Atomic
  • receive bloqueante — hoje devolve void na hora se a fila estiver vazia
  • TaskGroup e cancelamento
  • parallel tratando blocos em vez de instruções

Tudo isso está no roadmap.