Async, Concurrency & Parallelism
Modern programs do many things at once: download files while painting a UI, serve thousands of HTTP requests, process a million rows in parallel. AdeshLang supports all three flavors — threads, async/await, and parallel iteration — using the exact patterns from the examples repository.
Why concurrency matters
Without concurrency: read file → parse → respond → (idle) → next request
With concurrency: read file 1 ──┐
read file 2 ──┼── parse 1 ── respond 1
read file 3 ──┘
Waiting on I/O (network, disk, timers) wastes time a program could spend doing other work. Concurrency is how real servers handle thousands of users.
Threads: thread.spawn and join
A thread is a separate path of execution. spawn runs a function on a new
thread; join waits for it and can get its return value:
import thread;
let handle = thread.spawn(fn() {
print("Hello from worker thread!");
return 42;
});
print("Main thread continues...");
let result = handle.join();
print("Worker returned:", result);
// Hello from worker thread!
// Main thread continues...
// Worker returned: 42
Output:
Hello from worker thread!
Main thread continues...
Worker returned: 42
(order may vary)
From examples/Libraries/concurrency/01_basic_thread.adesh and 02_thread_join.adesh.
Passing messages with channels
A channel is a pipe between threads. The producer sends values; the consumer receives them — no shared memory, no locks:
import thread;
let pair = thread.Channel.bounded(8);
let tx = pair[0]; // sender
let rx = pair[1]; // receiver
thread.spawn(fn() {
tx.send(42);
tx.close();
}).join();
print(rx.recv().value); // 42
Output:
42
That is examples/Libraries/concurrency/12_channels.adesh verbatim. The
producer-consumer version with 8 items and a sum is
examples/Libraries/concurrency/20_producer_consumer.adesh.
Shared state with a mutex
When threads genuinely need to share and mutate data, protect it with a mutex (mutual exclusion lock):
import thread;
let mutex = thread.Mutex.new(0);
let t1 = thread.spawn(fn() { mutex.with(fn(v) { return v + 1; }); });
let t2 = thread.spawn(fn() { mutex.with(fn(v) { return v + 1; }); });
t1.join();
t2.join();
let g = mutex.lock();
print(g.value.get()); // 2 (both increments applied safely)
Output:
2
From examples/Libraries/concurrency/06_mutex.adesh and 05_shared_arc.adesh.
Atomics: lock-free counters
For simple counters, atomics are faster than locks:
import thread;
let counter = thread.Atomic.AtomicI64.new(0);
let h1 = thread.spawn(fn() { counter.fetch_add(1); });
let h2 = thread.spawn(fn() { counter.fetch_add(1); });
h1.join();
h2.join();
print(counter.load()); // 2
Output:
2
From examples/Libraries/concurrency/11_atomic_counter.adesh.
Thread pools & parallel iteration
Spawning a thread per tiny task is wasteful. A thread pool reuses worker threads:
import thread;
let pool = thread.ThreadPool.new(2);
let f = pool.submit(fn() { return 99; });
print(f.get()); // 99
pool.shutdown();
Output:
99
And for data-parallel work, AdeshLang includes the high-performance work-stealing Parallel module:
import Parallel;
let out = Parallel.map([1, 2, 3, 4], fn(x) { return x * 2; });
print(out); // [2, 4, 6, 8]
let sum = Parallel.sum([10, 20, 30, 40]);
print(sum); // 100
Output:
[2, 4, 6, 8]
100
From examples/Libraries/concurrency/parallel_ops.adesh.
Async/await: non-blocking single-threaded work
async fn starts a task and returns a Promise immediately; await waits
for it without blocking the whole thread (and main is auto-invoked):
async fn fetchUser(id) {
return "data_" + id;
}
async fn main() {
let result = await fetchUser(123);
print(result); // data_123
}
Output:
data_123
Promise.all runs several tasks and waits for all:
let pa = Promise(fn(resolve, reject) { resolve("A"); });
let pb = Promise(fn(resolve, reject) { resolve("B"); });
let pc = Promise(fn(resolve, reject) { resolve("C"); });
let all = await Promise.all([pa, pb, pc]);
print(all); // A B C
Output:
[A, B, C]
And .then() chains:
let p = Promise(fn(resolve, reject) { resolve(10); });
let chained = p.then(fn(v) { return v * 2; }).then(fn(v) { return v + 5; });
print(await chained); // 25
Output:
25
All of this is from examples/async/full_async_demo.adesh, which exercises
every async feature together.
Choosing the right tool
| You want to… | Use |
|---|---|
| Run a background task and collect its result | thread.spawn + join |
| Send data between threads safely | Channel |
| Protect shared mutable state | Mutex / RwLock |
| Count things very fast | AtomicI64 |
| Reuse threads for many small tasks | ThreadPool |
| Transform a big array on all cores | thread.parallel_map |
| Wait on I/O without blocking the thread | async fn + await |
| Race several futures / add timeouts | Promise.race, setTimeout |
The Fibonacci lesson
examples/fib/ shows why you care about performance: naive recursion
(fib_naive(35)) is exponentially slow; iterative DP (fib_dp) is linear:
fn fib_dp(n) {
if (n <= 1) { return n; }
let state = [0, 1];
let i = 2;
while (i <= n) {
let next = state[0] + state[1];
state = [state[1], next];
i = i + 1;
}
return state[1];
}
Combine that with --njit and you get the 10–230× speedups the docs brag
about.
Summary
✅ You learned:
thread.spawnruns a function on a new thread;joinwaits for itChannelpasses messages between threadsMutexandAtomicprotect shared mutable stateThreadPoolreuses workers;parallel_maptransforms arrays across coresasync fn+awaitdo non-blocking I/O workPromise.all/.then()compose async tasks- Choosing the right concurrency tool for the job
Next Step
Now that programs do things, let's prove they do them correctly — the built-in testing system. Continue to Testing →