All docsConcurrency

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.

venutian
make add(a, b)
    give a + b

t = spawn add(2, 3)
print kind(t)
print await t
output
Task
5

Several tasks spawned in a row make real concurrent progress against each other:

venutian
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
output
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:

venutian
make risky()
    give 1 / 0

failing_task = spawn risky()
attempt
    await failing_task
rescue e
    print "rescued:", e
output
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:

venutian
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
output
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.

venutian
use "stdlib/concurrent.ven"

doubled = map_over_concurrently([1, 2, 3], make(x) give x * 2)
print doubled
output
[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