Pular para o conteúdo

Fibras

Um stream action suspenso em cada emit — corrotina de verdade, com a limitação sem pilha dita com esse nome.

Uma fibra aqui não é aproximação. Um stream action da linguagem já é um gerador do Python, e o interpretador suspende o corpo dele em cada emit. O escalonador dirige esse gerador: o valor emitido diz o que a fibra está esperando, e a troca de contexto é o quadro do gerador.

dataforge
adopt Arcane.Laco as L

stream action trabalhador(nome, diario):
    cycle i from 1 to 3:
        diario.append($"{nome}{i}")
        emit L.ceder()

laco := L.novo()
diario := []
L.fibra(laco, trabalhador, ["A", diario])
L.fibra(laco, trabalhador, ["B", diario])

assert L.fibras(laco) is 2
L.rodar(laco)

// intercaladas: e isto que prova o escalonamento cooperativo
assert diario is ["A1", "B1", "A2", "B2", "A3", "B3"]
assert L.fibras(laco) is 0

Se fossem sequenciais o diário seria A1 A2 A3 B1 B2 B3. Elas se intercalam porque cada emit devolve o controle ao escalonador, que escolhe a próxima.

O que uma fibra pode esperar#

PedidoRetoma quando
L.ceder()na próxima volta — cede sem esperar nada
L.dormir(ms)passado o prazo, sem prender thread nenhuma
L.depois_de(ms, caixa, chave, valor)passado o prazo, com o valor já na caixa
L.ler(soquete, caixa, chave)houver o que ler; o dado chega pela caixa
L.escrever(soquete)der para escrever sem bloquear
L.esperar(outra)a outra fibra terminar

A lista é fechada: um vault qualquer emitido por engano vira erro com a lista, e não uma fibra parada para sempre esperando algo que ninguém registrou.

A caixa, e por que ela existe#

emit é instrução, não expressão: ele não devolve valor para a fibra. Então o laço entrega por um vault que a fibra passou — a caixa. Ser explícito aqui é melhor que fingir o contrário.

dataforge
adopt Arcane.Laco as L

stream action espera(caixa):
    emit L.depois_de(20, caixa, "resposta", "chegou")
    caixa["visto"] := caixa["resposta"]

laco := L.novo()
caixa := {"resposta": void, "visto": void}
L.fibra(laco, espera, [caixa])
L.rodar(laco)

assert caixa["visto"] is "chegou"

Sem pilha — e isso tem nome#

Cancelar#

dataforge
adopt Arcane.Laco as L

stream action longa(diario):
    diario.append("comecei")
    emit L.dormir(50)
    diario.append("nao chega aqui")

laco := L.novo()
diario := []
f := L.fibra(laco, longa, [diario])
L.apos(laco, 5, lambda => L.cancelar(f))
L.rodar(laco)

assert diario is ["comecei"]
assert L.cancelada(f)

Cancelar uma fibra fecha o gerador — o que roda os defer do corpo, como um halt faria. Uma thread do sistema não se cancela assim: é a vantagem concreta de a fibra ser um objeto e não um recurso do SO.