Concorrência Estruturada, Select e Worker Pool
Nesta página
scope { ... } implementa concorrência estruturada: todos os spawn
dentro do bloco são aguardados antes de continuar. Se qualquer filho entrar em
pânico, os irmãos são cancelados. Isso elimina tarefas órfãs e garante que os
recursos liberados após o scope estejam realmente limpos.
Dois produtores e um consumidor num mesmo scope; o programa só avança após todos terminarem.
// Feature: structured concurrency — `scope { spawn ... }` joins on exit
// Syntax: `scope { ... }` spawns child tasks and waits for all of
// them before continuing. A panic in any child cancels its siblings.
// When to use: any group of related coroutines that should live and
// die together. Avoids dangling tasks and orphaned futures.
let ch = channel(0)
scope {
spawn {
ch.send("first")
}
spawn {
ch.send("second")
}
spawn {
let a = ch.recv()
let b = ch.recv()
// Sort for deterministic output regardless of scheduler order.
let xs = [a.__val, b.__val]
if xs[0] < xs[1] {
print(xs[0])
print(xs[1])
} else {
print(xs[1])
print(xs[0])
}
}
}
print("scope-exited")
// expected:
// first
// second
// scope-exited
select aguarda múltiplos canais ao mesmo tempo e dispara o primeiro braço
pronto. Adicione after <duração> => { ... } para timeout e default => { ... }
para polling não-bloqueante.
Seleção entre dois canais, timeout com after 100ms e poll imediato com default.
// Feature: `select` — wait on multiple channels at once
// Syntax:
// select {
// x := <- chA => ...
// y := <- chB => ...
// after 50ms => ...
// default => ...
// }
// Fires the first arm that is ready. `select` blocks, so it must
// run inside a coroutine. `after Ns` adds a timeout. `default`
// fires immediately if nothing is ready.
// When to use: multi-source consumers, timeouts, non-blocking polls.
let a = channel(1)
let b = channel(1)
scope {
spawn {
sleep 1ms
a.send("from-a")
}
spawn {
// Only `a` will be ready, so this arm fires.
select {
x := <- a => { print("a: {x}") }
y := <- b => { print("b: {y}") }
}
}
}
// expected: a: from-a
// Timeout via `after <duration>`.
let c = channel(1)
scope {
spawn {
select {
x := <- c => { print("got: {x}") }
after 100ms => { print("timeout") }
}
}
}
// expected: timeout
// Non-blocking poll via `default`.
let d = channel(1)
scope {
spawn {
select {
x := <- d => { print("got: {x}") }
default => { print("nothing-ready") }
}
}
}
// expected: nothing-ready
O padrão worker pool combina tudo: um produtor distribui trabalho por um
canal jobs, N workers competem por itens e enviam resultados ao canal
results, e um agregador consome os resultados. channel(0) (rendezvous)
aplica contrapressão em todo o pipeline automaticamente.
1 produtor → 4 workers → 1 agregador, com scope garantindo o join de todas as tarefas.
// Feature: worker pool — fan-out + fan-in over channels
// Pattern: 1 producer feeds a `jobs` channel, N workers compete for jobs
// and push results into a `results` channel, 1 aggregator drains them.
// `scope { }` joins everything: when the last spawn finishes, the block
// exits. With `channel(0)` (rendezvous) you get backpressure for free —
// workers block on send until the aggregator is ready, and the producer
// blocks on send until a worker is ready.
let jobs = channel(0)
let results = channel(0)
let total = 8
scope {
spawn {
for i in 0..total { jobs.send(i) }
jobs.close()
}
// 4 workers — each loops on `for n in jobs` until the channel closes.
spawn { for n in jobs { results.send(n * n) } }
spawn { for n in jobs { results.send(n * n) } }
spawn { for n in jobs { results.send(n * n) } }
spawn { for n in jobs { results.send(n * n) } }
// Aggregator: knows how many results to expect, closes the channel
// when the last one lands so the scope can complete.
spawn {
var got = 0
for r in results {
print("result: {r}")
got += 1
if got == total { results.close() }
}
}
}
print("done")
// expected (in some interleaved order — squares of 0..7 then "done"):
// result: 0, 1, 4, 9, 16, 25, 36, 49
// done
Deadlocks são ruidosos, não silenciosos. Um
scopecujas tarefas ficam todas bloqueadas em canais sem nenhum desbloqueador possível lançadeadlock: ...em vez de travar para sempre.
Veja também