std::thread
Threads, and what they share: mutex<T> (a lock around a value, reached through a guard), cond (a condition variable), atomics, shared<T> (one value with an atomic count of its copies) and channel<T> (owned values passed between threads). No OS code here: the runtime starts and joins the system's threads and sleeps and wakes on a word (a futex on Linux, ulock on macOS, WaitOnAddress on Windows); locks are built on that word.
Source: std/thread.volt
Types
error std::thread::spawn_error
error spawn_errorwhy spawn failed
| Variant | |
|---|---|
OUT_OF_MEMORY | no memory for the thread's closure |
REFUSED | the system wouldn't start another thread |
struct std::thread::thread
struct threadA running thread. Deleting it joins it (waits for it to finish), so a thread never outlives its handle, and what its closure borrows (& captures) only has to outlive the handle.
| Field | |
|---|---|
handle: u64 = 0 | |
running: bool = false | false: nothing to join (a default thread, or one joined already) |
Methods
attach fn join(this: std::thread::thread&) -> voidwait for the thread to finish (again: nothing)
attach fn delete(this: std::thread::thread&) -> voidstruct std::thread::mutex
<T: type> struct mutexA lock around a value: lock() waits for it and gives a guard, through which the value is reached; deleting the guard unlocks it. Don't move a mutex while it's locked (share it: shared<mutex<T>>, or borrow it from a thread whose handle goes first).
| Field | |
|---|---|
word: i32 = 0 | 0 free, 1 locked, 2 locked and someone may be waiting |
value: T | what it guards |
Methods
<T: type> attach fn lock(this: std::thread::mutex<T>&) -> std::thread::guard<T>wait for the lock and take it: the guard reaches the value, and unlocks when it's deleted
<T: type> attach fn try_lock(this: std::thread::mutex<T>&) -> std::thread::guard<T>?the lock if it's free now, else null
struct std::thread::guard
<T: type> struct guardthe lock on a mutex, held until the guard is deleted
| Field | |
|---|---|
m: mutex<T>* |
Methods
<T: type> attach fn get(this: std::thread::guard<T>&) -> T&the locked value
<T: type> attach fn delete(this: std::thread::guard<T>&) -> voidstruct std::thread::cond
struct condA condition variable: wait(guard) unlocks the guard's mutex and sleeps until notified, then locks it again. It can wake without a notify, so wait in a loop that checks what it's waiting for.
| Field | |
|---|---|
seq: i32 = 0 | bumped by every notify |
Methods
<T: type> attach fn wait(this: std::thread::cond&, g: std::thread::guard<T>&) -> voidunlock g's mutex, sleep until notified (or not: check again), lock it again
attach fn notify_one(this: std::thread::cond&) -> voidwake one waiting thread
attach fn notify_all(this: std::thread::cond&) -> voidwake every waiting thread
struct std::thread::atomic_i64
struct atomic_i64an i64 that threads can change at the same time
| Field | |
|---|---|
value: i64 = 0 |
Methods
attach fn load(this: std::thread::atomic_i64&) -> i64attach fn store(this: std::thread::atomic_i64&, v: i64) -> voidattach fn add(this: std::thread::atomic_i64&, n: i64) -> i64add n; the value before
attach fn sub(this: std::thread::atomic_i64&, n: i64) -> i64subtract n; the value before
attach fn swap(this: std::thread::atomic_i64&, v: i64) -> i64set it to v; the value before
attach fn compare_swap(this: std::thread::atomic_i64&, expected: i64, desired: i64) -> boolset it to desired if it's expected; whether it was
struct std::thread::atomic_bool
struct atomic_boola bool that threads can change at the same time
| Field | |
|---|---|
value: i32 = 0 |
Methods
attach fn load(this: std::thread::atomic_bool&) -> boolattach fn store(this: std::thread::atomic_bool&, v: bool) -> voidattach fn swap(this: std::thread::atomic_bool&, v: bool) -> boolset it to v; the value before
struct std::thread::shared
<T: type, Allocator: std::mem::allocator = std::mem::default_allocator> struct sharedOne value with several owners, on any threads: copy makes another owner (the count goes up atomically), and the value is deleted with the last one. Reach the value with get(); to change it from several threads, share a mutex<T> or atomics.
| Field | |
|---|---|
ptr: shared_box<T>* | null in an empty (default) shared |
allocator: Allocator | what frees it |
Methods
<T: type, A: std::mem::allocator> attach fn get(this: std::thread::shared<T, A>&) -> T&the shared value
<T: type, A: std::mem::allocator> attach fn count(this: std::thread::shared<T, A>&) -> i64how many shared<T>s point at the value (on several threads, it may change right after)
<T: type, A: std::mem::allocator> attach fn copy(this: std::thread::shared<T, A>&) -> std::thread::shared<T, A>another owner of the same value
<T: type, A: std::mem::allocator> attach fn delete(this: std::thread::shared<T, A>&) -> voidone owner fewer; the last one deletes the value and frees its memory
struct std::thread::channel
<T: type, Allocator: std::mem::allocator = std::mem::default_allocator> struct channelOwned values passed between threads, first in first out. A copy of a channel is the same channel (give each thread its own copy); it's deleted, with whatever was never received, with the last. Its queue and shared state come from Allocator.
| Field | |
|---|---|
state: shared<chan_state<T, Allocator>, Allocator> |
Methods
<T: type> attach fn new(static this: std::thread::channel<T>) -> std::mem::mem_error!std::thread::channel<T>a new, empty channel
<T: type, A: std::mem::allocator> attach fn new_in(static this: std::thread::channel<T>, allocator: A) -> std::mem::mem_error!std::thread::channel<T, A>a new, empty channel whose memory comes from allocator
<T: type, A: std::mem::allocator> attach fn send(this: std::thread::channel<T, A>&, value: T) -> boolsend value to whoever receives; false if the channel is closed (then value is deleted)
<T: type, A: std::mem::allocator> attach fn recv(this: std::thread::channel<T, A>&) -> T?the next value, waiting for one if there's none yet; null once the channel is closed and empty
<T: type, A: std::mem::allocator> attach fn try_recv(this: std::thread::channel<T, A>&) -> T?the next value if one is waiting, else null
<T: type, A: std::mem::allocator> attach fn close(this: std::thread::channel<T, A>&) -> voidno more sends: receivers get what's left, then null
Functions
std::thread::spawn
<F: type, A: std::mem::allocator = std::mem::default_allocator> fn spawn(f: F, allocator: A = {}) -> spawn_error!threadrun f, a closure with no parameters, on a new thread. f is moved there: what it captures by value or with move belongs to the thread
std::thread::yield_now
fn yield_now() -> voidlet another thread run
std::thread::share
<T: type, A: std::mem::allocator = std::mem::default_allocator> fn share(value: T, allocator: A = {}) -> std::mem::mem_error!shared<T, A>value in a new shared<T>, its only owner so far