Concurrencia Estructurada, Select y Worker Pool
En esta página
scope { ... } implementa concurrencia estructurada: todos los spawn
dentro del bloque son esperados antes de continuar. Si algún hijo entra en
pánico, sus hermanos son cancelados. Esto elimina tareas huérfanas y garantiza
que los recursos liberados tras el scope estén realmente limpios.
Dos productores y un consumidor en el mismo scope; el programa solo avanza después de que todos terminen.
// 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 espera en múltiples canales al mismo tiempo y dispara el primer
brazo listo. Añade after <duración> => { ... } para timeout y
default => { ... } para polling no bloqueante.
Selección entre dos canales, timeout con after 100ms y poll inmediato con 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
El patrón worker pool combina todo: un productor distribuye trabajo por un
canal jobs, N workers compiten por ítems y envían resultados al canal
results, y un agregador consume los resultados. channel(0) (rendezvous)
aplica contrapresión en todo el pipeline automáticamente.
1 productor → 4 workers → 1 agregador, con scope garantizando el join de todas las tareas.
// 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
Los deadlocks son ruidosos, no silenciosos. Un
scopecuyas tareas quedan todas bloqueadas en canales sin ningún desbloqueador posible lanzadeadlock: ...en lugar de colgarse indefinidamente.
Consulta también