Guide
Concurrency
ilusm concurrency is message-passing. Spawn tasks with syn.run, communicate through channels with syn.chan. No shared mutable state across tasks.
Spawning a task
syn.run(f) runs a zero-argument function concurrently and returns a task handle. Call t.wai() to block until it finishes and get the result.
use syn
work() =
# do something
42
t = syn.run(work)
r = t.wai() # {val, err}
prn r.val # 42
prn r.err # nil (no error)
Use a lambda for inline tasks:
use syn
t = syn.run(\() 1 + 1)
r = t.wai()
prn r.val # 2
Handling task errors
If the task raises an error, wai() returns it in r.err instead of crashing:
use syn
bad() = err("something went wrong")
t = syn.run(bad)
r = t.wai()
if r.err:
prn $"task failed: {r.err}"
Channels
Channels let tasks communicate safely. Create with syn.chan() (unbuffered) or syn.chan(n) (buffered).
ch = syn.chan() # unbuffered - snd blocks until someone rcv's
ch = syn.chan(10) # buffered - snd is non-blocking up to 10 items
Send and receive
use syn
ch = syn.chan()
producer = syn.run(\()
i <- 0..4:
ch.snd(i)
ch.cls() # close when done
)
# receive until channel closes
whl tru:
val = ch.rcv()
if val == nil: brk # nil means channel closed
prn val
Receive with timeout
val = ch.rcv(5000) # wait up to 5000ms, nil on timeout
if val == nil: prn "timed out"
Fan-out: parallel tasks with a results channel
use syn
urls = [
"https://api.example.com/a",
"https://api.example.com/b",
"https://api.example.com/c"
]
ch = syn.chan(len(urls))
url <- urls:
u = url # capture for lambda
syn.run(\()
use net
res = net.get(u)
ch.snd({url: u, cod: res.cod})
)
i <- 0..len(urls)-1:
r = ch.rcv()
prn $"{r.url} → {r.cod}"
Pipeline with tasks
use syn
# Stage 1: produce numbers
produce(ch) =
i <- 0..9:
ch.snd(i)
ch.cls()
# Stage 2: double each
process(in_ch, out_ch) =
whl tru:
v = in_ch.rcv()
if v == nil: brk
out_ch.snd(v * 2)
out_ch.cls()
ch1 = syn.chan(5)
ch2 = syn.chan(5)
syn.run(\() produce(ch1))
syn.run(\() process(ch1, ch2))
whl tru:
v = ch2.rcv()
if v == nil: brk
prn v # 0, 2, 4, 6, ..., 18
The rule: no shared mutable state
Tasks in ilusm share the interpreter environment. Writing to the same variable from two tasks simultaneously is a data race. Always use channels to pass data between tasks - never mutate shared variables from concurrent tasks.
# BAD - data race
total = 0
t1 = syn.run(\() total = total + 1)
t2 = syn.run(\() total = total + 1)
# GOOD - use a channel
use syn
ch = syn.chan()
syn.run(\() ch.snd(1))
syn.run(\() ch.snd(1))
a = ch.rcv()
b = ch.rcv()
total = a + b