Standard library / worker_threads · on the one-page reference
worker_threads
Experimental — new, manually tested rather than conformance-suite covered.
Real CPU parallelism: a Lumen function runs on its own
native OS thread. Node's worker_threads loads a
separate script file into an isolated realm —
Lumen has no equivalent (one program compiles to one static binary, no
second module to dynamically load), so Worker.run takes a
Lumen function value instead of a file path, the
closest honest adaptation of the same idea. Deliberately a flat,
one-shot namespace call (Worker.run(fn)) rather than a
new Worker(...) object — Node's class shape exists for
a stateful, message-passing, long-lived entity
(postMessage/.on('message')/.terminate())
that Lumen has no channel primitive for yet; shipping the constructor
without any of the methods it implies would be worse than the flat shape
actually shipped.
Worker.run(fn)
(() => T) → Promise<T>, T one
of i32 | i64 | f64 | bool
— spawns a real, detached OS thread (one Worker = one thread, so
a CPU-bound call never queues behind unrelated work), runs
fn on it, and resolves the returned Promise
once it finishes. fn may be a plain named function (no
captures at all) or an arrow capturing only scalar outer bindings
— Lumen's
existing closure lowering copies each capture by value into a
heap-allocated environment at the closure's creation point, so a
captured scalar is a true snapshot with no live aliasing back to the
caller's binding.
function fib(): i32 {
let a: i32 = 0;
let b: i32 = 1;
let i: i32 = 0;
while (i < 40) {
const next = a + b;
a = b;
b = next;
i = i + 1;
}
return a;
}
async function main(): Promise<void> {
const p = Worker.run(fib);
console.log("worker launched, main keeps going");
console.log(await p);
}
What's safe to cross the thread boundary, stated
narrowly on purpose: only the four scalar types above, checker-
enforced — Lumen has no borrow checker and no
Send/Sync distinction, so strings, arrays,
Map/Set/Buffer, and class
instances are deliberately not accepted as T yet (not
verified safe without a real deep-copy/capture-safety analysis).
This does not, by itself, make every closure safe:
capturing a reference-shaped outer binding (an array,
Map/Set/Buffer, a class
instance) only copies the pointer/slice header into the worker's
closure — the pointee is still the same shared, unsynchronized
memory. If the main thread (or another Worker) mutates it while the
worker reads or writes it too, that's a real data race, the
same kind of trade-off http.createServer's concurrent
handlers already document above. Not statically prevented this pass.
For Map/Set specifically (spec 492) this is no
longer silent: each instance detects an overlapping access from another
thread and stops with an explicit runtime error instead of corrupting
its own storage or crashing somewhere unrelated — still not safe
to rely on, but no longer invisible when it happens. Arrays,
Buffer, and class instance fields have no such guard yet
and remain a real, silent data race.