class to create a lock-free unbounded work-stealing queue More...
#include <taskflow/core/wsq.hpp>
Public Types | |
| using | value_type = std::conditional_t<std::is_pointer_v<T>, T, std::optional<T>> |
| the return type of queue operations | |
Public Member Functions | |
| UnboundedWSQ (int64_t LogSize=TF_DEFAULT_UNBOUNDED_TASK_QUEUE_LOG_SIZE) | |
| constructs the queue with the given size in the base-2 logarithm | |
| ~UnboundedWSQ () | |
| destructs the queue | |
| bool | empty () const noexcept |
| queries if the queue is empty at the time of this call | |
| size_t | size () const noexcept |
| queries the number of items at the time of this call | |
| size_t | capacity () const noexcept |
| queries the capacity of the queue | |
| void | push (T item) |
| inserts an item to the queue | |
| template<typename I > | |
| void | bulk_push (I &first, size_t N) |
| tries to insert a batch of items into the queue | |
| value_type | pop () |
| pops out an item from the queue | |
| value_type | steal () |
| steals an item from the queue | |
| value_type | steal_with_feedback () |
| attempts to steal an item from the queue with three-state feedback | |
Static Public Member Functions | |
| static constexpr auto | empty_value () |
| returns the empty sentinel value for the queue element type | |
| static auto | contended_value () |
| returns the contended sentinel value for pointer element types | |
class to create a lock-free unbounded work-stealing queue
| T | data type (must be a pointer type) |
This class implements the work-stealing queue described in the paper, Correct and Efficient Work-Stealing for Weak Memory Models.
A work-stealing queue supports a single owner thread that performs push and pop operations, while multiple concurrent thief threads may steal tasks from the opposite end of the queue. The implementation is designed to operate correctly under weak memory models and uses atomic operations with carefully chosen memory orderings to ensure correctness and scalability.
Unlike bounded queues, this queue automatically grows its internal storage as needed, allowing it to accommodate an arbitrary number of tasks without a fixed capacity limit.
| using tf::UnboundedWSQ< T >::value_type = std::conditional_t<std::is_pointer_v<T>, T, std::optional<T>> |
the return type of queue operations
value_type represents the type returned by pop and steal operations. For pointer element types T, it is T itself and uses nullptr to indicate an empty result. For non-pointer types, it is std::optional<T>, where std::nullopt denotes the absence of a value.
This design avoids the overhead of std::optional for pointer types while providing a uniform empty-result semantics.
|
explicit |
constructs the queue with the given size in the base-2 logarithm
| LogSize | the base-2 logarithm of the queue size |
| void tf::UnboundedWSQ< T >::bulk_push | ( | I & | first, |
| size_t | N ) |
tries to insert a batch of items into the queue
| I | input iterator type |
| first | iterator to the first item in the batch |
| N | number of items to insert beginning at first |
This method pushes up to N items from the range [first, first + N) into the queue. The operation can trigger the queue to resize its capacity if more space is required. The iterator first is updated in place and will point to the next uninserted element after the call.
Bulk insertion is often faster than inserting elements one by one because it requires fewer atomic operations.
Only the owner thread can insert an item to the queue.
|
noexcept |
queries the capacity of the queue
|
inlinestatic |
returns the contended sentinel value for pointer element types
When steal_with_feedback returns this value, the queue was non-empty but the CAS was lost to another concurrent thief. The caller should retry the same victim immediately since work is known to exist.
Delegates to wsq_contended_value<T>(). See that function for a full explanation of the sentinel value and its safety guarantees.
|
noexcept |
queries if the queue is empty at the time of this call
|
inlinestaticconstexpr |
returns the empty sentinel value for the queue element type
This function provides a type-appropriate empty value used to indicate that a pop or steal operation failed. For pointer types, the empty value is nullptr of type T; for non-pointer types, it is std::nullopt of type std::optional<T>.
The function is implemented as a constexpr helper to avoid additional storage, runtime overhead, or code duplication across queue operations.
value_type representing the absence of an element. | UnboundedWSQ< T >::value_type tf::UnboundedWSQ< T >::pop | ( | ) |
pops out an item from the queue
This method pops an item from the queue. If the queue is empty, empty_value() is returned. The elements popped out from the queue follow a last-in-first-out (LIFO) order.
Only the owner thread can pop out an item from the queue.
| void tf::UnboundedWSQ< T >::push | ( | T | item | ) |
inserts an item to the queue
| item | the item to push to the queue |
This method pushes one item into the queue. The operation can trigger the queue to resize its capacity if more space is required.
Only the owner thread can insert an item to the queue.
|
noexcept |
queries the number of items at the time of this call
| UnboundedWSQ< T >::value_type tf::UnboundedWSQ< T >::steal | ( | ) |
steals an item from the queue
Any threads can try to steal an item from the queue. The return can be an empty_value() if this operation failed. The elements stolen from the queue follow a first-in-first-out (FIFO) order.
Multiple threads can simultaneously steal items from the queue.
| UnboundedWSQ< T >::value_type tf::UnboundedWSQ< T >::steal_with_feedback | ( | ) |
attempts to steal an item from the queue with three-state feedback
T):contended_value(): queue was non-empty but the CAS was lost to another concurrent thief; the caller should retry the same victim immediately since work is known to existempty_value() (nullptr): queue was genuinely empty; the caller should move on to a different victimFor non-pointer types T, the return is std::optional<T> and only two states are possible (value present or std::nullopt); contention cannot be distinguished from emptiness and the method behaves identically to steal.
The contended sentinel is reinterpret_cast<T>(uintptr_t{1}), i.e., the pointer value 0x1. This is guaranteed never to be a valid object pointer because the C++ standard and every major platform ABI require that any live object is aligned to at least alignof(T) bytes. For pointer element types used with this queue (e.g., Node*), alignof(Node) is at least 8 (and in Taskflow's case 64, due to alignas(TF_CACHELINE_SIZE) on Node members). Therefore the low bits of any real pointer are always zero, making 0x1 an impossible address. For void* there is no pointee type to check, but the same reasoning applies — no allocator ever returns address 0x1.
Multiple threads can simultaneously call this method.