Concurrency
Four keywords: spawn and await run real OS threads, and share and protect give them safe mutable state. No separate concurrency library to learn.
Concurrency in Venutian is four keywords: spawn, await, share, and protect. There is no separate library to learn first and no event loop to reason about; a spawned task runs on a real OS thread, starting immediately.
spawn and await
spawn takes a call and starts it running right now, on its own thread, returning a task handle immediately without waiting for it to finish. await blocks until that task completes and gives back its result.
make add(a, b)
give a + b
t = spawn add(2, 3)
print kind(t)
print await t
Task
5
Several tasks spawned in a row make real concurrent progress against each other:
make slow_double(n)
sleep(0.05)
give n * 2
t1 = spawn slow_double(1)
t2 = spawn slow_double(2)
t3 = spawn slow_double(3)
print await t1
print await t2
print await t3
2
4
6
All three sleeps overlap. The total time is close to one 0.05 second sleep, not three of them.
A fault surfaces at await
A fault raised inside a spawned task does not crash the thread silently. It is stored on the task and raised again the moment you await it, so attempt / rescue around the await catches it exactly like a fault from an ordinary call:
make risky()
give 1 / 0
failing_task = spawn risky()
attempt
await failing_task
rescue e
print "rescued:", e
rescued: Division by zero.
share and protect
A plain variable is not safe to write from two threads at once. share declares one that is meant to be, and protect marks the block that is allowed to touch it, taking a lock around that block for the duration:
share counter = 0
make increment_many(count)
i = 0
repeat while i < count
protect counter
counter = counter + 1
i = i + 1
tasks = []
repeat 8 times
tasks = appended(tasks, spawn increment_many(500))
repeat for t in tasks
await t
print counter
4000
Eight tasks each add 500 to counter, for exactly 4000 every time the program runs. Without protect, two threads could read the same value before either writes it back, and an update would be lost.
What spawn and await do not do
await always blocks until the task it names finishes; there is no nonblocking poll and no operation that waits for whichever of several tasks finishes first. Build a fixed size batch with a List of tasks and an await loop, as map_over_concurrently and worker_pool_map do in vconcurrent, rather than reaching for a select that does not exist.
vconcurrent
vconcurrent is a thin set of patterns built entirely on spawn and await, for the shape of concurrency a program reaches for most: running one function over a List of items concurrently, and collecting every task's result back in the same order the items came in.
use "stdlib/concurrent.ven"
doubled = map_over_concurrently([1, 2, 3], make(x) give x * 2)
print doubled
[2, 4, 6]
See the standard library for the rest of what ships, and the package index for vconcurrent's full reference.
Where to go next
- Errors for
attemptandrescueoutside a spawned task. - Standard library for
vconcurrentandvcluster. - Types and shapes for the Task type
kind()reports.