threads · API

threads.mutex

A pthread_mutex_t (and pthread_cond_t) you can share between threads.

Neither type has a portable size, so both are handled as an opaque 64-byte blob. That covers every libc this tin targets with room to spare: pthread_mutex_t is 40 bytes on glibc x86-64, 48 on musl x86-64, 8 on glibc aarch64 and 64 on macOS; pthread_cond_t is 48 on glibc and 48 on macOS. The blob is heap allocated, so its address is stable across moves of the owning Mojo value — which matters, because the address is what the worker threads see.

Owning vs. viewing

Mutex owns its blob: it initialises on construction, destroys and frees in __deinit__, and is move-only so there is exactly one owner. But a mutex is useless unless several threads reach the same one, and threads only get a raw context pointer. So Mutex.address() hands out the blob address and MutexRef.at(address) rebuilds a lock/unlock view on the worker side. The view is copyable and owns nothing.

Why there is no scope guard

The obvious with_lock / RAII Guard is deliberately missing. Mojo destroys a value at its last use, not at the end of its scope, so a guard whose only job is to unlock in __deinit__ gets destroyed — and therefore unlocks — at the point you stop mentioning it, which is usually the first line of the critical section. That is a silent correctness bug, not a compile error. Call lock() and unlock() explicitly, or use with_lock, which takes a thin function and brackets it properly.

Structs

MutexRef

struct MutexRef

A non-owning lock/unlock view of an initialised mutex blob.

This is what a worker thread reconstructs from an address it found in its context block. It initialises nothing and frees nothing.

Implements Copyable, ImplicitlyCopyable, Movable

at

def at(address: Int) -> Self

View the mutex whose blob starts at address.

Args
address: From Mutex.address().

address

def address(self) -> Int

Return the blob address.

lock

def lock(self)

Block until this thread holds the mutex.

unlock

def unlock(self)

Release the mutex. Only the holding thread may call this.

try_lock

def try_lock(self) -> Bool

Take the mutex if it is free, without blocking.

with_lock

def with_lock[body: def(Pointer[UInt8, MutUntrackedOrigin]) thin -> None](self, ctx: Pointer[UInt8, MutUntrackedOrigin])

Run body(ctx) with the mutex held, then release it.

Parameters
body: A thin, non-raising function. Non-raising is load-bearing: an escaping exception would skip the unlock.
Args
ctx: Passed straight through to body.

Mutex

struct Mutex

An owned pthread_mutex_t. Move-only; destroys and frees its blob.

Default (non-recursive, non-error-checking) attributes: locking one twice from the same thread deadlocks, and unlocking one you do not hold is undefined. That is plain POSIX behaviour, not something this wrapper adds.

Implements Movable

__init__

def __init__(out self)

Allocate and initialise the mutex.

Raises Error: If pthread_mutex_init fails.

__deinit__

def __deinit__(deinit self)

Destroy and free the mutex. Destroying a locked mutex is undefined under POSIX, so join every thread that can touch it first.

address

def address(self) -> Int

The blob address, to be stashed in a worker context block.

ref

def ref(self) -> MutexRef

A copyable non-owning view of this mutex.

lock

def lock(self)

Block until this thread holds the mutex.

unlock

def unlock(self)

Release the mutex.

try_lock

def try_lock(self) -> Bool

Take the mutex if it is free, without blocking.

with_lock

def with_lock[body: def(Pointer[UInt8, MutUntrackedOrigin]) thin -> None](self, ctx: Pointer[UInt8, MutUntrackedOrigin])

Run body(ctx) with the mutex held, then release it.

Parameters
body: A thin, non-raising function.
Args
ctx: Passed straight through to body.

CondVarRef

struct CondVarRef

A non-owning view of an initialised condition variable, paired with the mutex address the waiter holds.

Implements Copyable, ImplicitlyCopyable, Movable

at

def at(address: Int) -> Self

View the condition variable at address.

Args
address: From CondVar.address().

wait

def wait(self, mutex: MutexRef)

Atomically release mutex and block until signalled, then re-take it. Spurious wakeups are permitted by POSIX — always re-check your predicate in a loop.

Args
mutex: The mutex this thread currently holds.

signal

def signal(self)

Wake at least one waiter.

broadcast

def broadcast(self)

Wake every waiter.

CondVar

struct CondVar

An owned pthread_cond_t. Move-only; destroys and frees its blob.

Always used with a Mutex: take the mutex, check the predicate in a while, wait if it does not hold.

Implements Movable

__init__

def __init__(out self)

Allocate and initialise the condition variable.

Raises Error: If pthread_cond_init fails.

__deinit__

def __deinit__(deinit self)

Destroy and free the condition variable.

address

def address(self) -> Int

The blob address, to be stashed in a worker context block.

ref

def ref(self) -> CondVarRef

A copyable non-owning view.

wait

def wait(self, mutex: MutexRef)

Wait on this condition variable. See CondVarRef.wait.

Args
mutex: The mutex this thread currently holds.

signal

def signal(self)

Wake at least one waiter.

broadcast

def broadcast(self)

Wake every waiter.

Aliases

comptime MUTEX_BYTES = Int(64)

Size of the opaque pthread_mutex_t blob. Generous upper bound over glibc / musl / macOS on x86-64 and aarch64.

comptime COND_BYTES = Int(64)

Size of the opaque pthread_cond_t blob.

comptime BlobPtr = Pointer[UInt8, MutUntrackedOrigin]