Spawn and WaitGroups
Calibre provides spawn for running work concurrently.
The simplest form spawns a block of work.
let wg := spawn { => print("task A"), => print("task B")};The returned value is typically a WaitGroup, which can be used to wait until all spawned work is done.
wg.wait();You will often see this written together with defer or defer return.
let wg := spawn for i in 0..10 => { print("worker " & i);};
defer wg.wait();spawn for is a very common variation. It starts one concurrent task for each iteration of a loop.
This is useful when the same job structure should run across many inputs or worker ids.
let worker_count := 10;let wg := spawn for i in 0..worker_count => { print("spawned worker " & i);};Calibre also supports spawn@, which is useful for when you want the worker to be waited immediately. Note that spawn@ can be used anywhere spawn is applicable.
spawn@ for i in 0..100 => { print("num => " & i);};
spawn@ { => print("spawn block => A"), => print("spawn block => B")};Another variation is spawning inside a comprehension.
This creates a list whose elements are produced by spawned work. Note that the list may not be ordered.
let width := 32;
let concurrent := [i spawn for i in 0..=width];Wait groups are the main synchronization primitive for spawned work. The stdlib WaitGroup type provides:
WaitGroup.new().raw_add(...).raw_done().join(...).wait().count().wait_until_zero()
In everyday code, raw_add and raw_done should not be used as its generally better standard to combine them together using .join or using by using a spawn {...} block.
This feature mirrors goroutines in a lot of ways so checking the Go documentation can help if you’re still unsure about it.