Skip to content

Concurrency

Lisette follows Go’s concurrency model, where goroutines communicate via channels.

A task (goroutine) spawns concurrent work.

task long_running_job()
task {
let records = fetch_records()
write_report(records)
}

A Channel carries values between tasks.

let ch = Channel.new<int>()
task {
let value = heavy_computation()
ch.send(value)
}
if let Some(v) = ch.receive() {value sent by task
fmt.Println(v)
}
ch.close()

You can split a channel into sender and receiver:

let ch = Channel.new<int>()
let (tx, rx) = ch.split()tx is a Sender<int>, rx a Receiver<int>
task tx.send(42)
match rx.receive() {
Some(v) => fmt.Println(v),
None => fmt.Println("channel closed"),
}

For signaling without a value, use Channel<()>:

let done = Channel.new<()>()
task {
rebuild_index()
done.send(())
}
done.receive()blocks until task signals completion

A Channel can be iterated until closed:

let ch = Channel.buffered<int>(3)
ch.send(1)
ch.send(2)
ch.send(3)
ch.close()
for v in ch {
fmt.Println(v)prints 1, 2, 3
}

A Receiver<T> is iterable in the same way.

select waits on several channel operations and runs whichever is ready first:

let result = select {
match ch1.receive() {
Some(v) => v,
None => 0,
},
match ch2.receive() {
Some(v) => v * 2,
None => 0,
},
}

A shorthand receive arm destructures the Some case:

let result = select {
let Some(v) = ch.receive() => v,
_ => 0,
}

The _ arm runs immediately when no operation is ready, which makes the select non-blocking. It also catches the closed case for a shorthand receive arm.

A send arm in a select runs when the send completes:

select {
ch.send(42) => fmt.Println("sent"),
_ => fmt.Println("channel full"),
}

A send arm can panic with send on closed channel, unlike ch.send() outside a select, which returns false. Wrap the select in a recover block to guard against this.