Sync v0.1.0

Source
Locks, conditions, once-only initialization, semaphores, barriers, wait groups and channels for threads

Installation

Add Sync to the [Dependencies] table of your package's Rux.toml:

[Dependencies]
Sync = { Namespace = "Rux", Version = "0.1.0" }

Standard packages are declared by hand: the registry does not serve them, so rux add Rux/Sync cannot add them.

Dependencies

Guide

What it provides

ModuleCovers
LockLock, a bare lock, and Once, a once-only initialization; both can be module variables
MutexMutex<T> with its MutexGuard<T>, and Condition to wait on while holding one
RwLockRwLock<T> with ReadGuard<T> and WriteGuard<T>: many readers or one writer
CountersSemaphore, Barrier and WaitGroup
ChannelChannel<T>, a bounded queue any number of threads send to and receive from

The module names describe where each declaration comes from; they are not part of an import path.

Example

import Sync::Mutex;
import Thread::{ JoinHandle, Spawn };

func AddOne(total: Mutex<uint64>) -> () {
    let guard = total.Lock();
    *guard.Get() = *guard.Get() + 1u64;
}

func Main() -> int {
    let total = Mutex<uint64>(0u64);
    var first = Spawn(AddOne, total.Clone()) catch { else => return 1 };
    var second = Spawn(AddOne, total.Clone()) catch { else => return 1 };
    first.Join();
    second.Join();
    let guard = total.Lock();
    return *guard.Get() == 2u64 ? 0 : 2;
}

Design

Every type here waits the same way: a thread sleeps on a 32-bit word only while the word still holds the value it last saw, and the system checks that as it puts the thread to sleep, so a change that lands just before the sleep is never missed. That is futex on Linux, _umtx_op on FreeBSD, __ulock_wait on macOS and WaitOnAddress on Windows. Taking a free lock and releasing one nobody waits for are each one atomic instruction; only contention costs a system call.

Lock and Once live where they are declared and need no destroying, so a module variable can be one, starting as Lock::Unlocked or Once::Incomplete, and every thread names it directly. The other types are handles to storage their clones share: Clone gives another handle to move to another thread, and the storage, with the value a Mutex<T>, RwLock<T> or Channel<T> holds, is destroyed when the last handle and the last guard are gone. A guard is the only way to reach a guarded value, and it releases its lock when it is dropped. The value's address a guard hands out is valid only while the guard holds the lock.

None of the locks is reentrant, and none queues its waiters in order: RwLock can keep a writer waiting behind a steady stream of readers. A Condition wait can return without a notification, so it is always written in a loop that checks the condition. Running out of memory while creating one of these objects is a program error, as is misuse the type cannot recover from, such as marking a WaitGroup done more times than work was added.

Index

Types

NameSummary
Channel<T>A bounded queue of values passed between threads, any number of which may send and receive.
SemaphoreA count of permits that threads take and give back, waiting while none is left.
BarrierA meeting point a fixed number of threads wait at until all of them have arrived, then pass together.
WaitGroupA count of unfinished work that threads add to and mark done, and that others wait to see reach zero.
LockA lock that guards nothing in particular: whatever the code between Acquire and Release touches.
OnceRuns one initialization once, however many threads ask for it at once.
Mutex<T>A value guarded by a lock, shared between the threads that hold a handle to it.
MutexGuard<T>The lock on a Mutex<T>, held for as long as the guard lives, and the way to reach the value.
ConditionA condition threads wait on while holding a Mutex<T>, until another thread says it may hold.
RwLock<T>A value guarded by a lock that many readers or one writer may hold, shared between the threads that hold a handle.
ReadGuard<T>A shared hold on an RwLock<T>: the value may be read, and other readers may hold it too.
WriteGuard<T>The exclusive hold on an RwLock<T>: the value may be read and written, and nobody else holds the lock.