st3/lib.rs
1//! # St³ — Stealing Static Stack
2//!
3//! Very fast lock-free, bounded, work-stealing queue with FIFO stealing and
4//! LIFO or FIFO semantic for the worker thread.
5//!
6//! The `Worker` handle enables push and pop operations from a single thread,
7//! while `Stealer` handles can be shared between threads to perform FIFO
8//! batch-stealing operations.
9//!
10//! `St³` is effectively a faster, fixed-size alternative to the Chase-Lev
11//! double-ended queue. It uses no atomic fences, much fewer atomic loads and
12//! stores, and fewer Read-Modify-Write operations: none for `push`, one for
13//! `pop` and one (LIFO) or two (FIFO) for `steal`.
14//!
15//! ## Example
16//!
17//! ```
18//! use std::thread;
19//! use st3::lifo::Worker;
20//!
21//! // Push 4 items into a queue of capacity 256.
22//! let worker = Worker::new(256);
23//! worker.push("a").unwrap();
24//! worker.push("b").unwrap();
25//! worker.push("c").unwrap();
26//! worker.push("d").unwrap();
27//!
28//! // Steal items concurrently.
29//! let stealer = worker.stealer();
30//! let th = thread::spawn(move || {
31//! let other_worker = Worker::new(256);
32//!
33//! // Try to steal half the items and return the actual count of stolen items.
34//! match stealer.steal(&other_worker, |n| n/2) {
35//! Ok(actual) => actual,
36//! Err(_) => 0,
37//! }
38//! });
39//!
40//! // Pop items concurrently.
41//! let mut pop_count = 0;
42//! while worker.pop().is_some() {
43//! pop_count += 1;
44//! }
45//!
46//! // Does it add up?
47//! let steal_count = th.join().unwrap();
48//! assert_eq!(pop_count + steal_count, 4);
49//! ```
50#![warn(missing_docs, missing_debug_implementations, unreachable_pub)]
51#![no_std]
52
53extern crate alloc;
54
55use alloc::boxed::Box;
56use alloc::vec::Vec;
57
58use core::fmt;
59use core::mem::MaybeUninit;
60
61use config::{UnsignedLong, UnsignedShort};
62
63use crate::loom_exports::cell::UnsafeCell;
64
65mod config;
66pub mod fifo;
67pub mod lifo;
68mod loom_exports;
69
70/// Error returned when stealing is unsuccessful.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum StealError {
73 /// No item was stolen.
74 Empty,
75 /// Another concurrent stealing operation is ongoing.
76 Busy,
77}
78
79impl fmt::Display for StealError {
80 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81 match self {
82 StealError::Empty => write!(f, "cannot steal from empty queue"),
83 StealError::Busy => write!(f, "a concurrent steal operation is ongoing"),
84 }
85 }
86}
87
88#[inline]
89/// Pack two short integers into a long one.
90fn pack(value1: UnsignedShort, value2: UnsignedShort) -> UnsignedLong {
91 ((value1 as UnsignedLong) << UnsignedShort::BITS) | value2 as UnsignedLong
92}
93#[inline]
94/// Unpack a long integer into 2 short ones.
95fn unpack(value: UnsignedLong) -> (UnsignedShort, UnsignedShort) {
96 (
97 (value >> UnsignedShort::BITS) as UnsignedShort,
98 value as UnsignedShort,
99 )
100}
101
102fn allocate_buffer<T>(len: usize) -> Box<[UnsafeCell<MaybeUninit<T>>]> {
103 let mut buffer = Vec::with_capacity(len);
104
105 // Note: resizing the vector would normally be an O(N) operation due to
106 // initialization, but initialization is optimized out in release mode since
107 // an `UnsafeCell<MaybeUninit>` does not actually need to be initialized as
108 // `UnsafeCell` is `repr(transparent)`.
109 buffer.resize_with(len, || UnsafeCell::new(MaybeUninit::uninit()));
110
111 buffer.into_boxed_slice()
112}