Dragon

Scoped and Manual OS Threads

Green threads are the right default - cheap, numerous, and great at I/O. But some work wants a real OS thread: a CPU-bound computation that should run on its own hardware core, or a long-lived worker whose lifetime you manage by hand. Dragon's other two tiers cover exactly those: thread { } for a scoped thread that joins itself, and the Thread class for one you control.

Scoped OS threads: thread { }

When you need true hardware parallelism for a CPU-bound chunk of work, use a thread { } block:

thread {
    compute_heavy_task()
    write_results_to_disk()
}
# the thread has joined here - guaranteed

thread is a contextual keyword. The block becomes a real OS thread, and Dragon inserts the join automatically at the end of the enclosing scope. You cannot forget to join it - when control leaves the scope, the thread is done. This is RAII for hardware threads.

Launch several and let them all join together at the scope boundary:

thread { process_batch_a() }
thread { process_batch_b() }
# both have joined before execution continues past this point

You can even await inside a thread { } block, mixing tiers freely - a green thread running on top of the scoped OS thread:

async def stage() -> None {
    print("runs on a green thread")
}

thread {
    await stage()
}

Manual threads: the Thread class

For a thread whose lifetime you control - a long-running daemon, a worker you start and join at different points - use the Thread class from the threading module. Its API matches Python's threading.Thread:

from threading import Thread

def worker() -> None {
    print("working")
}

t: Thread = Thread(target=worker)
t.start()
# ... do other things ...
if t.is_alive() {
    print("still running")
}
t.join()

Thread gives you three methods: start() launches it, is_alive() reports whether it's still running, and join() waits for it to finish. Note that Thread.join() returns a status code (an int), not a value - if you want a result back from concurrent work, that's exactly what the green-thread Task[T] is for. OS threads are for running work in parallel; green-thread tasks are for collecting a typed result.

Choosing a tier

Need a typed result back?           → green thread: t: Task[T] = fire work()
A CPU burst, no result, no fuss?    → thread { ... }   (auto-joins at scope exit)
A worker you start/stop yourself?   → Thread(target=fn)

At a glance

You want to...Write
A CPU burst that auto-joinsthread { ... }
Several parallel burstsmultiple thread { } blocks in a scope
A thread you controlt: Thread = Thread(target=fn)
Start / check / waitt.start() / t.is_alive() / t.join()

Real OS threads share memory, which means coordinating their access to shared data. That's the last chapter of this part: Synchronization

  • locks, semaphores, and the rest.