# threads.mutex

> A module of threads's API.

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`

```mojo
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`

```mojo
def at(address: Int) -> Self
```

View the mutex whose blob starts at `address`.

Args:

- `address`: From `Mutex.address()`.

#### `address`

```mojo
def address(self) -> Int
```

Return the blob address.

#### `lock`

```mojo
def lock(self)
```

Block until this thread holds the mutex.

#### `unlock`

```mojo
def unlock(self)
```

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

#### `try_lock`

```mojo
def try_lock(self) -> Bool
```

Take the mutex if it is free, without blocking.

#### `with_lock`

```mojo
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`

```mojo
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__`

```mojo
def __init__(out self)
```

Allocate and initialise the mutex.

Raises: Error: If `pthread_mutex_init` fails.

#### `__deinit__`

```mojo
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`

```mojo
def address(self) -> Int
```

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

#### `ref`

```mojo
def ref(self) -> MutexRef
```

A copyable non-owning view of this mutex.

#### `lock`

```mojo
def lock(self)
```

Block until this thread holds the mutex.

#### `unlock`

```mojo
def unlock(self)
```

Release the mutex.

#### `try_lock`

```mojo
def try_lock(self) -> Bool
```

Take the mutex if it is free, without blocking.

#### `with_lock`

```mojo
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`

```mojo
struct CondVarRef
```

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

Implements: `Copyable`, `ImplicitlyCopyable`, `Movable`

#### `at`

```mojo
def at(address: Int) -> Self
```

View the condition variable at `address`.

Args:

- `address`: From `CondVar.address()`.

#### `wait`

```mojo
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`

```mojo
def signal(self)
```

Wake at least one waiter.

#### `broadcast`

```mojo
def broadcast(self)
```

Wake every waiter.

### `CondVar`

```mojo
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__`

```mojo
def __init__(out self)
```

Allocate and initialise the condition variable.

Raises: Error: If `pthread_cond_init` fails.

#### `__deinit__`

```mojo
def __deinit__(deinit self)
```

Destroy and free the condition variable.

#### `address`

```mojo
def address(self) -> Int
```

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

#### `ref`

```mojo
def ref(self) -> CondVarRef
```

A copyable non-owning view.

#### `wait`

```mojo
def wait(self, mutex: MutexRef)
```

Wait on this condition variable. See `CondVarRef.wait`.

Args:

- `mutex`: The mutex this thread currently holds.

#### `signal`

```mojo
def signal(self)
```

Wake at least one waiter.

#### `broadcast`

```mojo
def broadcast(self)
```

Wake every waiter.

## Aliases

### `MUTEX_BYTES`

```mojo
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.

### `COND_BYTES`

```mojo
comptime COND_BYTES = Int(64)
```

Size of the opaque `pthread_cond_t` blob.

### `BlobPtr`

```mojo
comptime BlobPtr = Pointer[UInt8, MutUntrackedOrigin]
```
