Mutexes
std.sync.Mutex[T] holds a value that several workers change through shared
references or shared owners, one lock holder at a
time. Use it when several values must
change together to keep an invariant; a single independent counter or flag is
an atomic. The operations are listed in
std.sync.
Locking
with place.lock() as value: borrows the mutex at place shared, waits until
no other thread holds the lock, and binds value: &mut T for the block. as
may be omitted. The place is a name, a field, an element, or a dereference,
including a &Mutex[T] parameter, or a Shared[Mutex[T]] handle, which locks
the mutex through *handle; a temporary is rejected. lock() anywhere
other than a with manager is a compile-time error naming that form.
The lock is released on every exit from the block: its end, return, ?,
break, and continue. Comma-separated managers nest, so
with a.lock() as x, b.lock() as y: takes a then b and releases b first.
value is an ordinary exclusive reference. Its loan belongs to the lock, not to
the mutex’s owner, which stays shared-borrowed until the block ends: the mutex
cannot be moved, replaced, or consumed while locked. Like any local reference,
value cannot be stored, returned, captured by an anonymous function, or used
after the block.
into_inner() consumes an owned mutex and returns its value without locking.
Ownership proves that no thread holds or waits for the lock.
Waits the compiler rejects
A lock waits as long as another holder keeps it. The compiler rejects these patterns that would wait forever:
- Relocking in the block. Locking a place that an enclosing lock block in
the same function already holds is an error. Places are compared as written:
mand*mmatch. - Nesting locks that could alias. Locking a
Mutex[T]inside a lock block that holds anotherMutex[T]of the sameTis an error when either place is reached through a reference, aSharedhandle, or a computed index (cells[i]). Such places can name one mutex (r = &a,b = a.share(),i == j), and only types are compared. Two owned places built from local names, fields, and constant indices, such asaandborcells[0]andcells[1], are distinct mutexes and may nest. Otherwise wrap one value in a distinct class. A relock inside a called function is not detected and deadlocks at run time. - Joining under a lock. A scoped task cannot be joined, by
task.join()or by leaving itswithblock, while the joining function holds a lock on aMutex[T]and the worker can reach aMutex[T]of the sameT: as an argument or capture, a field or element of one, or behind a reference. The worker could be waiting for that lock. Mutexes are matched by type, like the channel rule. Start the task outside the lock:with spawn(...)?:aroundwith m.lock():unlocks before joining. A join inside a function called while the lock is held is not detected and deadlocks at run time. - Waiting for a pool job under a lock. A job submitted to a
pool records the
Mutex[T]types it can reach, including throughSharedhandles.future.result()on its future, and a shutdown of any pool after it was submitted (shutdown(),drop, reassignment, or the end of the pool’s scope), is an error while the function holds a lock on aMutex[T]of a recordedT. A future is traced like the channel rule traces it, through local bindings and calls that return it. - Yielding under a lock.
yieldinside a lock block is an error: a suspended generator would keep the lock indefinitely.
Locking two mutexes in different orders on different threads can still deadlock; take nested locks in one fixed order.
No poisoning
A trap terminates the process without unwinding, so no program continues after
a holder fails mid-update. A Plenty mutex therefore has no poisoned state and
lock() has no error result.
Not provided
There is no try_lock, timed lock, reentrant lock, condition variable,
read-write lock, or fairness guarantee, and no unlocked access through
&mut Mutex[T] (Rust’s get_mut); lock it instead. Executor and pool jobs own
their inputs and cannot borrow a mutex; give each job a share of a
Shared[Mutex[T]] instead.
Lowering
The state word is 0 when unlocked, 1 when locked, and 2 when locked with possible waiters. Lock is an inline compare-exchange from 0 to 1; on failure the runtime exchanges in 2 and sleeps on the word with a Linux futex until an exchange finds 0. Unlock is an inline exchange to 0 that calls the runtime to wake one waiter only if it replaced 2. See the runtime mechanisms.