introduce mpsc_weak_queue / bounded_ticket_queue

Add Dmitry Vyukov's queues
This commit is contained in:
Tim Blechmann
2026-05-19 17:34:36 +08:00
parent 3580859264
commit ac8a3e44da
18 changed files with 3215 additions and 7 deletions
+3
View File
@@ -19,6 +19,8 @@ target_include_directories(boost_lockfree INTERFACE include)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.23 AND BOOST_LOCKFREE_USE_FILE_SET)
set(Headers
include/boost/lockfree/bounded_ticket_queue.hpp
include/boost/lockfree/mpsc_weak_queue.hpp
include/boost/lockfree/spsc_queue.hpp
include/boost/lockfree/spsc_value.hpp
include/boost/lockfree/policies.hpp
@@ -28,6 +30,7 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.23 AND BOOST_LOCKFREE_USE_FILE_SET)
include/boost/lockfree/detail/copy_payload.hpp
include/boost/lockfree/detail/freelist.hpp
include/boost/lockfree/detail/parameter.hpp
include/boost/lockfree/detail/power_of_two.hpp
include/boost/lockfree/detail/prefix.hpp
include/boost/lockfree/detail/tagged_ptr.hpp
include/boost/lockfree/detail/tagged_ptr_dcas.hpp
+25 -4
View File
@@ -1,7 +1,7 @@
[library Boost.Lockfree
[quickbook 1.4]
[authors [Blechmann, Tim]]
[copyright 2008-2011 Tim Blechmann]
[copyright 2008-2026 Tim Blechmann]
[category algorithms]
[purpose
lockfree concurrent data structures
@@ -115,15 +115,25 @@ lock-freedom:
[h2 Data Structures]
_lockfree_ implements four lock-free data structures:
_lockfree_ implements six lock-free data structures:
[variablelist
[[[classref boost::lockfree::queue]]
[a lock-free multi-producer/multi-consumer queue]
[a lock-free multi-producer/multi-consumer queue (Based on Michael & Scott's algorithm)]
]
[[[classref boost::lockfree::bounded_ticket_queue]]
[a bounded multi-producer/multi-consumer queue based on the Dyukov turnstile
(Vyukov bounded MPMC algorithm). Configurable via `single_producer<>` and
`single_consumer<>` policy tags to obtain wait-free push or pop.]
]
[[[classref boost::lockfree::mpsc_weak_queue]]
[a multi-producer/single-consumer queue (Based on Dmitry Vyukov's algorithm)]
]
[[[classref boost::lockfree::stack]]
[a lock-free multi-producer/multi-consumer stack]
[a lock-free multi-producer/multi-consumer stack (Based on Treiber's algorithm)]
]
[[[classref boost::lockfree::spsc_queue]]
@@ -160,6 +170,16 @@ The data structures can be configured with [@boost:/libs/parameter/doc/html/inde
[[[classref boost::lockfree::allow_multiple_reads]]
[Configures the [classref boost::lockfree::spsc_value] to allow the content to be read multiple times.]
]
[[[classref boost::lockfree::single_producer]]
[Configures a data structure for single-producer usage. When set to `true`, push
does not use a CAS and is wait-free. Only valid for [classref boost::lockfree::bounded_ticket_queue].]
]
[[[classref boost::lockfree::single_consumer]]
[Configures a data structure for single-consumer usage. When set to `true`, pop
does not use a CAS and is wait-free. Only valid for [classref boost::lockfree::bounded_ticket_queue].]
]
]
@@ -278,6 +298,7 @@ _lockfree_ requires a c++14 compliant compiler. Users of MSVC are strongly recom
# [@http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.37.3574 Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms by Michael Scott and Maged Michael],
In Symposium on Principles of Distributed Computing, pages 267275, 1996.
# [@http://books.google.com/books?id=pFSwuqtJgxYC M. Herlihy & Nir Shavit. The Art of Multiprocessor Programming], Morgan Kaufmann Publishers, 2008
# [@http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue Multiple Producer Single Consumer Lock-Free Queue by Dmitry Vyukov], 2010
[endsect]
@@ -0,0 +1,586 @@
// lock-free bounded ticket queue
// based on Dmitry Vyukov's bounded MPMC queue algorithm
// (the "Dyukov turnstile"): each cell carries a monotonically
// increasing ticket that gates producer and consumer access.
//
// Copyright (C) 2026 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_LOCKFREE_BOUNDED_TICKET_QUEUE_HPP_INCLUDED
#define BOOST_LOCKFREE_BOUNDED_TICKET_QUEUE_HPP_INCLUDED
#include <boost/config.hpp>
#ifdef BOOST_HAS_PRAGMA_ONCE
# pragma once
#endif
#include <boost/assert.hpp>
#include <boost/core/allocator_access.hpp>
#include <boost/core/span.hpp>
#include <boost/parameter/optional.hpp>
#include <boost/parameter/parameters.hpp>
#include <boost/lockfree/detail/atomic.hpp>
#include <boost/lockfree/detail/copy_payload.hpp>
#include <boost/lockfree/detail/parameter.hpp>
#include <boost/lockfree/detail/power_of_two.hpp>
#include <boost/lockfree/detail/prefix.hpp>
#include <boost/lockfree/detail/uses_optional.hpp>
#include <boost/lockfree/lockfree_forward.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#if defined( _MSC_VER )
# pragma warning( push )
# pragma warning( disable : 4324 )
#endif
namespace boost { namespace lockfree {
#ifndef BOOST_DOXYGEN_INVOKED
namespace detail {
typedef parameter::parameters< boost::parameter::optional< tag::allocator >,
boost::parameter::optional< tag::capacity >,
boost::parameter::optional< tag::single_producer >,
boost::parameter::optional< tag::single_consumer > >
bounded_ticket_queue_signature;
struct multi_producer_tag
{};
struct single_producer_tag
{};
struct multi_consumer_tag
{};
struct single_consumer_tag
{};
} /* namespace detail */
#endif
/** Bounded ticket queue based on Dmitry Vyukov's bounded MPMC
* queue (the "Dyukov turnstile")
* (<a href="https://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue">reference</a>).
*
* The queue uses a fixed-size ring buffer of cells, each
* protected by a monotonically increasing ticket (sequence
* number). Producers and consumers coordinate solely by
* advancing this ticket.
*
* The data structure is linearizable.
*
* \par Caveats
* - **Multi-producer push is not lock-free.** A concurrent
* producer that has claimed a slot via CAS but is then
* suspended can block all other producers once the ring
* buffer wraps around to that slot. In practice preemption
* is brief, but hard real-time systems should prefer a
* single-producer configuration.
* - **Multi-consumer pop is not lock-free.** Symmetric
* stall risk as multi-producer push. Prefer
* `single_consumer<true>` for hard real-time.
* - **Unfairness.** The CAS-based slot reservation provides no
* FIFO or fairness guarantee.
*
* \par Policies
* - \ref boost::lockfree::capacity (optional):\n
* Sets the queue capacity at compile time. Implies a static
* storage buffer and precludes specifying an allocator.
* - \ref boost::lockfree::allocator (default \c std::allocator<void>).\n
* Allocator used for the cell storage. Only valid when the
* capacity is specified at run time.
* - \ref boost::lockfree::single_producer (default \c false):\n
* If true, only one thread calls push. Push becomes wait-free.
* - \ref boost::lockfree::single_consumer (default \c false):\n
* If true, only one thread calls pop. Pop becomes wait-free.
*
* \par Requirements
* - T must be copy- or move-constructible, depending on which
* \c push overload is used.
* - T must be move-constructible and move-assignable.
*/
template < typename T, typename... Options >
class bounded_ticket_queue
{
private:
#ifndef BOOST_DOXYGEN_INVOKED
typedef typename detail::bounded_ticket_queue_signature::bind< Options... >::type bound_args;
static constexpr bool has_capacity = detail::extract_capacity< bound_args >::has_capacity;
static constexpr size_t compile_time_capacity = detail::extract_capacity< bound_args >::capacity;
static constexpr bool is_single_producer = detail::extract_single_producer< bound_args >::value;
static constexpr bool is_single_consumer = detail::extract_single_consumer< bound_args >::value;
typedef std::conditional_t< is_single_producer, detail::single_producer_tag, detail::multi_producer_tag > producer_tag;
typedef std::conditional_t< is_single_consumer, detail::single_consumer_tag, detail::multi_consumer_tag > consumer_tag;
struct cell
{
detail::atomic< size_t > sequence;
alignas( T ) std::array< unsigned char, sizeof( T ) > storage_buffer;
template < typename U >
void construct_from( U&& v, size_t pos )
{
::new ( storage_buffer.data() ) T( std::forward< U >( v ) );
sequence.store( pos, detail::memory_order_release );
}
T take()
{
struct destroy_on_exit
{
cell& c;
~destroy_on_exit()
{
reinterpret_cast< T* >( c.storage_buffer.data() )->~T();
}
} guard { *this };
T* ptr = reinterpret_cast< T* >( storage_buffer.data() );
return std::move( *ptr );
}
};
// ---- allocator ----
typedef detail::extract_allocator_t< bound_args, cell > cell_allocator;
static constexpr bool has_allocator_arg = detail::extract_allocator< bound_args, cell >::has_allocator;
static_assert( !( has_capacity && has_allocator_arg ),
"bounded_ticket_queue: capacity and allocator are mutually exclusive" );
// ---- storage policies ----
struct alignas( detail::cacheline_bytes ) compile_time_storage
{
compile_time_storage()
{
for ( cell& c : buffer_ )
c.sequence.store( &c - buffer_.data(), detail::memory_order_relaxed );
}
static constexpr size_t buffer_capacity_ = compile_time_capacity;
static constexpr size_t buffer_mask_ = compile_time_capacity - 1;
static constexpr bool is_pow2_ = detail::is_power_of_two( compile_time_capacity );
std::array< cell, compile_time_capacity > buffer_ {};
cell& get_cell( size_t pos )
{
size_t index = is_pow2_ ? ( pos & buffer_mask_ ) : ( pos % buffer_capacity_ );
return buffer_[ index ];
}
};
struct alignas( detail::cacheline_bytes ) runtime_storage : private cell_allocator
{
runtime_storage() = default;
runtime_storage( size_t n, cell_allocator const& alloc = {} ) :
cell_allocator( alloc ),
buffer_capacity_ { n },
buffer_mask_ { detail::is_power_of_two( n ) ? n - 1 : 0 }
{
BOOST_ASSERT( n >= 2 );
buffer_ = cell_allocator::allocate( n );
for ( cell* c = buffer_; c != buffer_ + n; ++c )
c->sequence.store( c - buffer_, detail::memory_order_relaxed );
}
~runtime_storage()
{
if ( buffer_ )
cell_allocator::deallocate( buffer_, buffer_capacity_ );
}
cell* buffer_ = nullptr;
const size_t buffer_capacity_ = 0;
const size_t buffer_mask_ = 0; /* valid if capacity is power of two, else 0 */
cell& get_cell( size_t pos )
{
size_t index = buffer_mask_ ? ( pos & buffer_mask_ ) : ( pos % buffer_capacity_ );
return buffer_[ index ];
}
};
typedef std::conditional_t< has_capacity, compile_time_storage, runtime_storage > storage_type;
struct implementation_defined
{
typedef cell_allocator allocator;
typedef std::size_t size_type;
};
#endif
public:
typedef T value_type;
typedef typename implementation_defined::allocator allocator;
typedef typename implementation_defined::size_type size_type;
/** \returns true if the implementation is lock-free.
*
* With single-producer and single-consumer the queue is
* wait-free (no CAS used). For multi-consumer or
* multi-producer configurations only the atomic positions
* used with CAS are checked.
*/
bool is_lock_free() const
{
return is_lock_free_impl( producer_tag(), consumer_tag() );
}
// ---- constructors ----
/** Construct a queue with compile-time capacity.
*
* Storage is allocated in a static buffer; no heap
* allocation is performed.
*
* \pre Must specify a \c capacity<> argument >= 2.
*/
bounded_ticket_queue()
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
requires( has_capacity )
#endif
{}
/** Construct a queue with runtime capacity.
*
* \param n Queue capacity. Must be >= 2. Not required to be
* a power of two, but power-of-two sizes use a
* fast masking path.
*
* \pre Must \b not specify a \c capacity<> argument.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
explicit bounded_ticket_queue( size_type n )
requires( !has_capacity )
#else
template < typename Enabler = std::enable_if< !has_capacity > >
explicit bounded_ticket_queue( size_type n )
#endif
:
storage_( n )
{}
/** Construct a queue with runtime capacity and custom allocator.
*
* \param n Queue capacity. Must be >= 2.
* \param alloc Allocator used for the cell storage. Its
* \c value_type is rebound to \c cell.
*
* \pre Must \b not specify a \c capacity<> argument.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
bounded_ticket_queue( size_type n, allocator const& alloc )
requires( !has_capacity )
#else
template < typename Enabler = std::enable_if< !has_capacity > >
bounded_ticket_queue( size_type n, allocator const& alloc )
#endif
:
storage_( n, alloc )
{}
bounded_ticket_queue( const bounded_ticket_queue& ) = delete;
bounded_ticket_queue& operator=( const bounded_ticket_queue& ) = delete;
bounded_ticket_queue( bounded_ticket_queue&& ) = delete;
bounded_ticket_queue& operator=( bounded_ticket_queue&& ) = delete;
/** Destroys the queue.
*/
~bounded_ticket_queue()
{
consume_all( []( T&& ) {} );
}
// ---- capacity / empty ----
/** Check whether the queue is empty.
*
* \returns true if the queue is empty, false otherwise.
*
* \note The result is only accurate when no other thread
* modifies the queue concurrently.
*/
bool empty() const
{
size_t dp = dequeue_pos_.load( detail::memory_order_acquire );
size_t ep = enqueue_pos_.load( detail::memory_order_acquire );
return dp == ep;
}
/** \returns the capacity of the queue.
*/
size_type capacity() const
{
return storage_.buffer_capacity_;
}
// ---- push ----
/** Pushes a value to the queue.
*
* \returns true if the value was pushed, false if the queue
* is full.
*
* \note Thread-safe. Multiple producers may call push
* concurrently. Never allocates memory.
*/
bool push( const T& t )
{
return do_push( t );
}
bool push( T&& t )
{
return do_push( std::move( t ) );
}
// ---- pop ----
/** Pops an element from the queue.
*
* \returns true if an element was popped, false if the queue
* was empty.
*/
bool pop( T& ret )
{
return pop< T >( ret );
}
/** Pops an element from the queue.
*
* \tparam U type to receive the popped value; \c U must be
* constructible from \c T&& and assignable from
* \c U.
* \returns true if an element was popped, false if the queue
* was empty.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
template < typename U >
requires( std::is_constructible< U, T && >::value )
#else
template < typename U, typename Enabler = typename std::enable_if< std::is_constructible< U, T&& >::value >::type >
#endif
bool pop( U& ret )
{
return consume_one( [ & ]( T&& arg ) {
ret = U( std::forward< T >( arg ) );
} );
}
#if !defined( BOOST_NO_CXX17_HDR_OPTIONAL ) || defined( BOOST_DOXYGEN_INVOKED )
/** Pops an element, returning a std::optional<T>.
*
* \returns std::optional containing the value on success,
* std::nullopt if the queue is empty.
*/
std::optional< T > pop( uses_optional_t )
{
T to_dequeue;
if ( pop( to_dequeue ) )
return to_dequeue;
else
return std::nullopt;
}
/** Pops an element, returning a std::optional.
*
* \tparam U type to receive the popped value; T must be
* convertible to U.
* \returns std::optional&lt;U&gt; containing the value on
* success, std::nullopt if the queue is empty.
*/
template < typename U >
std::optional< U > pop( uses_optional_t )
{
U to_dequeue;
if ( pop( to_dequeue ) )
return to_dequeue;
else
return std::nullopt;
}
#endif
// ---- batch consumption ----
/** Consumes one element via a functor.
*
* Moves one element out of the slot, passes the resulting
* rvalue to the functor, and releases the slot for reuse
* once the functor returns.
*
* \returns true if an element was consumed.
*/
template < typename Functor >
bool consume_one( Functor&& f )
{
return consume_one_impl( std::forward< Functor >( f ), consumer_tag() );
}
/** Consumes all elements via a functor.
*
* Sequentially pops all elements and applies the functor to
* each.
*
* \returns the number of elements consumed.
*/
template < typename Functor >
size_t consume_all( Functor&& f )
{
size_t element_count = 0;
while ( consume_one( f ) )
element_count += 1;
return element_count;
}
private:
#ifndef BOOST_DOXYGEN_INVOKED
// ---- push implementations ----
template < typename U >
bool do_push_impl( U&& t, detail::multi_producer_tag )
{
size_t pos = enqueue_pos_.load( detail::memory_order_relaxed );
for ( ;; ) {
cell& c = storage_.get_cell( pos );
size_t seq = c.sequence.load( detail::memory_order_acquire );
intptr_t dif = intptr_t( seq ) - intptr_t( pos );
if ( dif == 0 ) {
if ( enqueue_pos_.compare_exchange_weak( pos, pos + 1, detail::memory_order_relaxed ) ) {
c.construct_from( std::forward< U >( t ), pos + 1 );
return true;
}
} else if ( dif < 0 ) {
return false;
} else {
pos = enqueue_pos_.load( detail::memory_order_relaxed );
}
}
}
template < typename U >
bool do_push_impl( U&& t, detail::single_producer_tag )
{
size_t pos = enqueue_pos_.load( detail::memory_order_relaxed );
cell& c = storage_.get_cell( pos );
size_t seq = c.sequence.load( detail::memory_order_acquire );
intptr_t dif = intptr_t( seq ) - intptr_t( pos );
if ( dif == 0 ) {
enqueue_pos_.store( pos + 1, detail::memory_order_relaxed );
c.construct_from( std::forward< U >( t ), pos + 1 );
return true;
}
return false;
}
template < typename U >
bool do_push( U&& t )
{
return do_push_impl( std::forward< U >( t ), producer_tag() );
}
// ---- consume_one implementations ----
template < typename Functor >
bool consume_one_impl( Functor&& f, detail::multi_consumer_tag )
{
size_t pos = dequeue_pos_.load( detail::memory_order_relaxed );
for ( ;; ) {
cell& c = storage_.get_cell( pos );
size_t seq = c.sequence.load( detail::memory_order_acquire );
intptr_t dif = intptr_t( seq ) - intptr_t( pos + 1 );
if ( dif == 0 ) {
if ( dequeue_pos_.compare_exchange_weak( pos, pos + 1, detail::memory_order_relaxed ) ) {
f( c.take() );
c.sequence.store( pos + storage_.buffer_capacity_, detail::memory_order_release );
return true;
}
} else if ( dif < 0 ) {
return false;
} else {
pos = dequeue_pos_.load( detail::memory_order_relaxed );
}
}
}
template < typename Functor >
bool consume_one_impl( Functor&& f, detail::single_consumer_tag )
{
size_t pos = dequeue_pos_.load( detail::memory_order_relaxed );
cell& c = storage_.get_cell( pos );
size_t seq = c.sequence.load( detail::memory_order_acquire );
if ( seq != pos + 1 )
return false;
f( c.take() );
c.sequence.store( pos + storage_.buffer_capacity_, detail::memory_order_release );
dequeue_pos_.store( pos + 1, detail::memory_order_relaxed );
return true;
}
// ---- is_lock_free implementations ----
bool is_lock_free_impl( detail::single_producer_tag, detail::single_consumer_tag ) const
{
return true;
}
bool is_lock_free_impl( detail::single_producer_tag, detail::multi_consumer_tag ) const
{
return dequeue_pos_.is_lock_free();
}
bool is_lock_free_impl( detail::multi_producer_tag, detail::single_consumer_tag ) const
{
return enqueue_pos_.is_lock_free();
}
bool is_lock_free_impl( detail::multi_producer_tag, detail::multi_consumer_tag ) const
{
return enqueue_pos_.is_lock_free() && dequeue_pos_.is_lock_free();
}
// ---- data members ----
alignas( detail::cacheline_bytes ) storage_type storage_;
alignas( detail::cacheline_bytes ) detail::atomic< size_t > enqueue_pos_ { 0 };
alignas( detail::cacheline_bytes ) detail::atomic< size_t > dequeue_pos_ { 0 };
#endif
};
}} // namespace boost::lockfree
#if defined( _MSC_VER )
# pragma warning( pop )
#endif
#endif /* BOOST_LOCKFREE_BOUNDED_TICKET_QUEUE_HPP_INCLUDED */
@@ -520,6 +520,14 @@ public:
deallocate< ThreadSafe >( index );
}
template < bool ThreadSafe >
void destruct( index_t index )
{
T* n = NodeStorage::nodes() + index;
n->~T();
deallocate< ThreadSafe >( index );
}
template < bool ThreadSafe >
void destruct( T* n )
{
@@ -673,6 +681,81 @@ using select_freelist_t = typename select_freelist< T, Alloc, IsCompileTimeSized
//----------------------------------------------------------------------------------------------------------------------
template < typename T, typename Alloc = std::allocator< T > >
class alignas( cacheline_bytes ) direct_allocator : Alloc
{
public:
typedef T* index_t;
typedef T* tagged_node_handle;
template < typename Allocator >
direct_allocator( Allocator const& alloc, std::size_t = 0 ) :
Alloc( alloc )
{}
template < bool ThreadSafe >
void reserve( std::size_t )
{}
template < bool ThreadSafe, bool Bounded >
T* construct( void )
{
T* node = Alloc::allocate( 1 );
if ( node )
new ( node ) T();
return node;
}
template < bool ThreadSafe, bool Bounded, typename ArgumentType >
T* construct( ArgumentType&& arg )
{
T* node = Alloc::allocate( 1 );
if ( node )
new ( node ) T( std::forward< ArgumentType >( arg ) );
return node;
}
template < bool ThreadSafe, bool Bounded, typename ArgumentType1, typename ArgumentType2 >
T* construct( ArgumentType1&& arg1, ArgumentType2&& arg2 )
{
T* node = Alloc::allocate( 1 );
if ( node )
new ( node ) T( std::forward< ArgumentType1 >( arg1 ), std::forward< ArgumentType2 >( arg2 ) );
return node;
}
template < bool ThreadSafe >
void destruct( tagged_node_handle node )
{
if ( node ) {
node->~T();
Alloc::deallocate( node, 1 );
}
}
bool is_lock_free( void ) const
{
return false;
}
T* get_handle( T* pointer ) const
{
return pointer;
}
T* get_pointer( T* tptr ) const
{
return tptr;
}
T* null_handle( void ) const
{
return nullptr;
}
};
//----------------------------------------------------------------------------------------------------------------------
template < typename T, bool IsNodeBased >
struct select_tagged_handle
{
@@ -82,6 +82,15 @@ template < typename bound_args, bool default_ = false >
using extract_allow_multiple_reads
= extract_integral_arg_or_default_t< bound_args, tag::allow_multiple_reads, bool, default_ >;
template < typename bound_args, bool default_ = false >
using extract_freelist = extract_integral_arg_or_default_t< bound_args, tag::freelist, bool, default_ >;
template < typename bound_args, bool default_ = false >
using extract_single_producer = extract_integral_arg_or_default_t< bound_args, tag::single_producer, bool, default_ >;
template < typename bound_args, bool default_ = false >
using extract_single_consumer = extract_integral_arg_or_default_t< bound_args, tag::single_consumer, bool, default_ >;
//----------------------------------------------------------------------------------------------------------------------
}}} // namespace boost::lockfree::detail
@@ -0,0 +1,23 @@
// boost lockfree: small power-of-two helper
//
// Copyright (C) 2026 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_LOCKFREE_DETAIL_POWER_OF_TWO_HPP_INCLUDED
#define BOOST_LOCKFREE_DETAIL_POWER_OF_TWO_HPP_INCLUDED
#include <cstddef>
namespace boost { namespace lockfree { namespace detail {
inline constexpr bool is_power_of_two( size_t n ) noexcept
{
return n && ( n & ( n - 1 ) ) == 0;
}
}}} // namespace boost::lockfree::detail
#endif /* BOOST_LOCKFREE_DETAIL_POWER_OF_TWO_HPP_INCLUDED */
@@ -59,6 +59,9 @@ class spsc_queue;
template < typename T, typename... Options >
struct spsc_value;
template < typename T, typename... Options >
class bounded_ticket_queue;
}} // namespace boost::lockfree
#endif // BOOST_DOXYGEN_INVOKED
+674
View File
@@ -0,0 +1,674 @@
// lock-free multi-producer/single-consumer queue
// based on Dmitry Vyukov's MPSC queue algorithm
//
// Copyright (C) 2026 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_LOCKFREE_MPSC_WEAK_QUEUE_HPP_INCLUDED
#define BOOST_LOCKFREE_MPSC_WEAK_QUEUE_HPP_INCLUDED
#include <boost/config.hpp>
#ifdef BOOST_HAS_PRAGMA_ONCE
# pragma once
#endif
#include <boost/assert.hpp>
#include <boost/core/allocator_access.hpp>
#include <boost/parameter/optional.hpp>
#include <boost/parameter/parameters.hpp>
#include <boost/static_assert.hpp>
#include <boost/lockfree/detail/atomic.hpp>
#include <boost/lockfree/detail/freelist.hpp>
#include <boost/lockfree/detail/parameter.hpp>
#include <boost/lockfree/detail/uses_optional.hpp>
#include <boost/lockfree/lockfree_forward.hpp>
#include <type_traits>
#if defined( _MSC_VER )
# pragma warning( push )
# pragma warning( disable : 4324 ) // structure was padded due to __declspec(align())
#endif
#if defined( BOOST_INTEL ) && ( BOOST_INTEL_CXX_VERSION > 1000 )
# pragma warning( push )
# pragma warning( disable : 488 ) // template parameter unused in declaring parameter types
#endif
namespace boost { namespace lockfree {
#ifndef BOOST_DOXYGEN_INVOKED
namespace detail {
typedef parameter::parameters< boost::parameter::optional< tag::allocator >,
boost::parameter::optional< tag::capacity >,
boost::parameter::optional< tag::freelist >,
boost::parameter::optional< tag::fixed_sized > >
mpsc_weak_queue_signature;
} /* namespace detail */
#endif
/** Multi-producer/single-consumer queue based on Dmitry Vyukov's MPSC algorithm.
*
* The push and pop methods execute in a bounded number of instructions
* (wait-free execution), but the queue as a whole is not lock-free in
* the strict sense (see Limitations).
*
* By default, a freelist manages node memory. Freed nodes are recycled
* internally rather than returned to the OS until the queue is destroyed.
*
* \par Limitations
* A good writeup of the limitations of this algorithm is available at
* <a href="https://int08h.com/post/ode-to-a-vyukov-queue/">Ode to a Vyukov Queue</a>.
*
* - \b Push atomicity: push exchanges the tail then links the previous
* node. If a producer thread terminates or stalls between these steps,
* the queue becomes inconsistent. The consumer will see a broken link
* and stop delivering elements until the producer completes the link.
* If the producer permanently terminates, the queue is permanently broken.
* - \b Non-linearizable: per-producer FIFO is preserved, but no global
* ordering across concurrent producers exists. pop may return false
* even if the queue is non-empty when a concurrent push is in progress.
*
* \par Policies
* - \ref boost::lockfree::freelist (default \c true):\n
* When enabled, freed nodes enter an internal freelist for reuse,
* avoiding repeated allocation. When disabled, the allocator handles
* every allocation and deallocation. Allocator operations may not be
* lock-free.
* - \ref boost::lockfree::fixed_sized (default \c false):\n
* When enabled, push never performs dynamic allocation, guaranteeing
* lock-free behavior. Nodes reside in a fixed array addressed by
* index. Capacity is limited to the index type's range (typically
* 2^16-2). This is required for lock-free operation on platforms
* lacking double-width CAS.
* - \ref boost::lockfree::capacity (optional):\n
* Sets queue size at compile time. Implies \c fixed_sized<true>.
* - \ref boost::lockfree::allocator (default \c std::allocator<void>).
*
* \par Requirements
* - T must be move-constructible.
* - T must be move-assignable.
*
* \note Only one consumer thread is supported. Multiple producer threads
* are supported.
*/
template < typename T, typename... Options >
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
requires( std::is_move_constructible_v< T >, std::is_move_assignable_v< T > )
#endif
class mpsc_weak_queue
{
private:
#ifndef BOOST_DOXYGEN_INVOKED
typedef typename detail::mpsc_weak_queue_signature::bind< Options... >::type bound_args;
static constexpr bool use_freelist_default = true;
static constexpr bool use_freelist = detail::extract_freelist< bound_args, use_freelist_default >::value;
static constexpr bool has_capacity = detail::extract_capacity< bound_args >::has_capacity;
static constexpr size_t capacity
= detail::extract_capacity< bound_args >::capacity + 1; // the queue uses one dummy node
static constexpr bool fixed_sized = detail::extract_fixed_sized< bound_args >::value;
static constexpr bool node_based = !use_freelist || !( has_capacity || fixed_sized );
static constexpr bool compile_time_sized = use_freelist && has_capacity;
static constexpr bool capacity_without_freelist = has_capacity && !use_freelist;
static_assert( capacity_without_freelist == false,
"capacity<> argument cannot be used without freelist<> argument" );
static constexpr bool can_reserve = use_freelist;
struct BOOST_MAY_ALIAS node
{
typedef typename detail::select_tagged_handle< node, node_based >::handle_type handle_type;
template < typename TagT >
node( T const& v, handle_type null_handle, TagT /*next_tag*/ ) :
next( null_handle ),
data( v )
{}
template < typename TagT >
node( T&& v, handle_type null_handle, TagT /*next_tag*/ ) :
next( null_handle ),
data( std::move( v ) )
{}
template < typename TagT >
node( handle_type null_handle, TagT /*next_tag*/ ) :
next( null_handle )
{}
atomic< handle_type > next;
T data;
};
typedef detail::extract_allocator_t< bound_args, node > node_allocator;
typedef std::conditional_t< use_freelist,
detail::select_freelist_t< node, node_allocator, compile_time_sized, fixed_sized, capacity >,
detail::direct_allocator< node, node_allocator > >
pool_t;
typedef typename pool_t::tagged_node_handle tagged_node_handle;
typedef typename detail::select_tagged_handle< node, node_based >::handle_type handle_type;
void initialize()
{
node* n = pool.template construct< true, false >( pool.null_handle() );
handle_type dummy_handle = pool.get_handle( n );
tail_ = dummy_handle;
head_.store( dummy_handle, memory_order_release );
}
struct implementation_defined
{
typedef node_allocator allocator;
typedef std::size_t size_type;
};
#endif
public:
typedef T value_type;
typedef typename implementation_defined::allocator allocator;
typedef typename implementation_defined::size_type size_type;
/**
* \returns true if the implementation is lock-free.
*
* \warning Only checks whether the head, tail, and freelist can be
* modified in a lock-free manner. On most platforms the entire
* implementation is lock-free when this returns true.
*/
bool is_lock_free() const
{
return head_.is_lock_free() && pool.is_lock_free();
}
/** Construct a queue.
*
* Requires a capacity<> argument when freelist is enabled.
*/
mpsc_weak_queue()
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
requires( has_capacity || !use_freelist )
#endif
:
pool( node_allocator(), capacity )
{
BOOST_ASSERT( has_capacity || !use_freelist );
initialize();
}
/** Construct a fixed-sized queue with a custom allocator.
*
* Requires a capacity<> argument when freelist is enabled.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
template < typename U >
requires( has_capacity || !use_freelist )
#else
template < typename U, typename Enabler = std::enable_if< has_capacity || !use_freelist > >
#endif
explicit mpsc_weak_queue( typename boost::allocator_rebind< node_allocator, U >::type const& alloc ) :
pool( alloc, capacity )
{
initialize();
}
/** Construct a fixed-sized queue with a custom allocator.
*
* Requires a capacity<> argument when freelist is enabled.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
explicit mpsc_weak_queue( allocator const& alloc )
requires( has_capacity || !use_freelist )
#else
template < typename Enabler = std::enable_if< has_capacity || !use_freelist > >
explicit mpsc_weak_queue( allocator const& alloc )
#endif
:
pool( alloc, capacity )
{
initialize();
}
/** Construct a variable-sized queue.
*
* Allocates \c n nodes initially for the freelist.
*
* Requires that no capacity<> argument is specified and that
* freelist is enabled.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
explicit mpsc_weak_queue( size_type n )
requires( !has_capacity && use_freelist )
#else
template < typename Enabler = std::enable_if< !has_capacity && use_freelist > >
explicit mpsc_weak_queue( size_type n )
#endif
:
pool( node_allocator(), n + 1 )
{
initialize();
}
/** Construct a variable-sized queue with a custom allocator.
*
* Allocates \c n nodes initially for the freelist.
*
* Requires that no capacity<> argument is specified and that
* freelist is enabled.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
mpsc_weak_queue( size_type n, allocator const& alloc )
requires( !has_capacity && use_freelist )
#else
template < typename Enabler = std::enable_if< !has_capacity && use_freelist > >
mpsc_weak_queue( size_type n, allocator const& alloc )
#endif
:
pool( alloc, n + 1 )
{
initialize();
}
mpsc_weak_queue( const mpsc_weak_queue& ) = delete;
mpsc_weak_queue& operator=( const mpsc_weak_queue& ) = delete;
mpsc_weak_queue( mpsc_weak_queue&& ) = delete;
mpsc_weak_queue& operator=( mpsc_weak_queue&& ) = delete;
/** \copydoc boost::lockfree::stack::reserve
* */
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
void reserve( size_type n )
requires( can_reserve )
#else
template < typename Enabler = std::enable_if< can_reserve > >
void reserve( size_type n )
#endif
{
pool.template reserve< true >( n );
}
/** \copydoc boost::lockfree::stack::reserve_unsafe
* */
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
void reserve_unsafe( size_type n )
requires( can_reserve )
#else
template < typename Enabler = std::enable_if< can_reserve > >
void reserve_unsafe( size_type n )
#endif
{
pool.template reserve< false >( n );
}
/** Destroys the queue and frees all nodes from the freelist.
*/
~mpsc_weak_queue()
{
consume_all( []( T&& ) {} );
pool.template destruct< false >( head_.load( memory_order_relaxed ) );
}
/** Check whether the queue is empty.
*
* \returns true if the queue is empty, false otherwise.
*
* \note The result is only accurate when no other thread modifies the
* queue concurrently.
*/
bool empty() const
{
return head_.load( memory_order_acquire ) == tail_;
}
/** Pushes a value to the queue.
*
* \returns true if the value was pushed, false if node allocation failed.
*
* \note Thread-safe. Multiple producers may call push concurrently.
* When the memory pool is exhausted and the pool is not fixed-sized,
* a new node is allocated via the allocator, which may not be
* lock-free.
*/
bool push( const T& t )
{
return do_push< false >( t );
}
/// \copydoc boost::lockfree::mpsc_weak_queue::push(const T & t)
bool push( T&& t )
{
return do_push< false >( std::forward< T >( t ) );
}
/** Pushes a value to the queue without allocating.
*
* \returns true if the value was pushed, false if the freelist is empty.
*
* \note Thread-safe. Multiple producers may call bounded_push
* concurrently. Unlike push, bounded_push never allocates memory;
* it fails when the freelist is exhausted.
* \throws std::bad_alloc if the allocator throws during node
* construction.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
bool bounded_push( const T& t )
requires( use_freelist )
#else
template < typename Enabler = std::enable_if< use_freelist > >
bool bounded_push( const T& t )
#endif
{
return do_push< true >( t );
}
/// \copydoc boost::lockfree::mpsc_weak_queue::bounded_push(const T & t)
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
bool bounded_push( T&& t )
requires( use_freelist )
#else
template < typename Enabler = std::enable_if< use_freelist > >
bool bounded_push( T&& t )
#endif
{
return do_push< true >( std::forward< T >( t ) );
}
private:
#ifndef BOOST_DOXYGEN_INVOKED
template < bool Bounded >
bool do_push( T&& t )
{
node* n = pool.template construct< true, Bounded >( std::forward< T >( t ), pool.null_handle() );
return do_push_node( n );
}
template < bool Bounded >
bool do_push( T const& t )
{
node* n = pool.template construct< true, Bounded >( t, pool.null_handle() );
return do_push_node( n );
}
bool do_push_node( node* n )
{
if ( n == nullptr )
return false;
handle_type node_handle = pool.get_handle( n );
handle_type old_head = head_.exchange( node_handle, memory_order_acq_rel );
node* old_head_ptr = pool.get_pointer( old_head );
old_head_ptr->next.store( node_handle, memory_order_release );
return true;
}
#endif
public:
/** Pushes a value to the queue without synchronization.
*
* \returns true if the value was pushed, false if node allocation failed.
*
* \note Not thread-safe. Only one producer thread should call
* unsynchronized_push. When the memory pool is exhausted and
* the pool is not fixed-sized, a new node is allocated via the
* allocator, which may not be lock-free.
* \throws std::bad_alloc if the allocator throws during node
* construction.
*/
bool unsynchronized_push( const T& t )
{
return unsynchronized_push_impl( t );
}
/// \copydoc boost::lockfree::mpsc_weak_queue::unsynchronized_push(const T& t)
bool unsynchronized_push( T&& t )
{
return unsynchronized_push_impl( std::forward< T >( t ) );
}
private:
#ifndef BOOST_DOXYGEN_INVOKED
template < typename U >
bool unsynchronized_push_impl( U&& t )
{
node* n = pool.template construct< false, false >( std::forward< U >( t ), pool.null_handle() );
if ( n == nullptr )
return false;
handle_type node_handle = pool.get_handle( n );
handle_type old_head = head_.load( memory_order_relaxed );
node* old_head_ptr = pool.get_pointer( old_head );
old_head_ptr->next.store( node_handle, memory_order_relaxed );
head_.store( node_handle, memory_order_release );
return true;
}
#endif
public:
/** Pops an element from the queue.
*
* \returns true if an element was popped, false if the queue was empty.
*
* \note Thread-safe and wait-free. Only one consumer thread should call
* pop. The output argument may be modified even when the operation
* fails. pop may return false even if the queue is non-empty when
* a concurrent push is in progress (see Limitations).
*/
bool pop( T& ret )
{
return pop< T >( ret );
}
/** Pops an element from the queue.
*
* \tparam U type to receive the popped value; U must be constructible
* from T (explicit or implicit).
* \returns true if an element was popped, false if the queue was empty.
*
* \note Thread-safe and wait-free. Only one consumer thread should call
* pop. The output argument may be modified even when the operation
* fails. pop may return false even if the queue is non-empty when
* a concurrent push is in progress (see Limitations).
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
template < typename U >
requires( std::is_constructible_v< U, T && > )
#else
template < typename U, typename Enabler = std::enable_if_t< std::is_constructible< U, T&& >::value > >
#endif
bool pop( U& ret )
{
return consume_one( [ & ]( T&& arg ) {
ret = U( std::forward< T >( arg ) );
} );
}
#if !defined( BOOST_NO_CXX17_HDR_OPTIONAL ) || defined( BOOST_DOXYGEN_INVOKED )
/** Pops an element, returning a std::optional<T>.
*
* \returns std::optional containing the value on success,
* std::nullopt if the queue is empty.
*
* \note Thread-safe and wait-free. Only one consumer thread should call
* this overload.
*/
std::optional< T > pop( uses_optional_t )
{
T to_dequeue;
if ( pop( to_dequeue ) )
return to_dequeue;
else
return std::nullopt;
}
/** Pops an element, returning a std::optional.
*
* \tparam U type to receive the popped value; T must be convertible to U.
* \returns std::optional&lt;U&gt; containing the value on success,
* std::nullopt if the queue is empty.
*
* \note Thread-safe and wait-free. Only one consumer thread should call
* this overload.
*/
template < typename U >
std::optional< U > pop( uses_optional_t )
{
U to_dequeue;
if ( pop( to_dequeue ) )
return to_dequeue;
else
return std::nullopt;
}
#endif
/** Pops an element without synchronization.
*
* \returns true if an element was popped, false if the queue was empty.
*
* \note Not thread-safe but wait-free. Only one consumer thread should
* call unsynchronized_pop. The output argument may be modified even
* when the operation fails.
*/
bool unsynchronized_pop( T& ret )
{
return unsynchronized_pop< T >( ret );
}
/** Pops an element without synchronization.
*
* \tparam U type to receive the popped value; U must be constructible
* from T (explicit or implicit).
* \returns true if an element was popped, false if the queue was empty.
*
* \note Not thread-safe but wait-free. Only one consumer thread should
* call unsynchronized_pop.
*/
#if !defined( BOOST_NO_CXX20_HDR_CONCEPTS )
template < typename U >
requires( std::is_constructible_v< U, T && > )
#else
template < typename U, typename Enabler = std::enable_if_t< std::is_constructible< U, T&& >::value > >
#endif
bool unsynchronized_pop( U& ret )
{
return unsynchronized_consume_one( [ & ]( T&& arg ) {
ret = U( std::forward< T >( arg ) );
} );
}
/** Consumes one element via a functor.
*
* Pops one element and applies the functor to it.
*
* \returns true if an element was consumed.
*
* \note Thread-safe and wait-free when the functor is thread-safe and
* non-blocking. Only one consumer thread should call this.
*/
template < typename Functor >
bool consume_one( Functor&& f )
{
handle_type tail = tail_;
node* tail_ptr = pool.get_pointer( tail );
handle_type next = tail_ptr->next.load( memory_order_acquire );
node* next_ptr = pool.get_pointer( next );
if ( next_ptr == 0 ) {
handle_type head = head_.load( memory_order_acquire );
if ( tail == head )
return false;
return false;
}
f( std::move( next_ptr->data ) );
tail_ = next;
pool.template destruct< true >( tail );
return true;
}
/** Consumes one element via a functor without synchronization.
*
* Pops one element and applies the functor to it.
*
* \returns true if an element was consumed.
*
* \note Not thread-safe but wait-free. Only one consumer thread should
* call this.
*/
template < typename Functor >
bool unsynchronized_consume_one( Functor&& f )
{
handle_type tail = tail_;
node* tail_ptr = pool.get_pointer( tail );
handle_type next = tail_ptr->next.load( memory_order_relaxed );
node* next_ptr = pool.get_pointer( next );
if ( next_ptr == 0 ) {
handle_type head = head_.load( memory_order_relaxed );
if ( tail == head )
return false;
return false;
}
f( std::move( next_ptr->data ) );
tail_ = next;
pool.template destruct< false >( tail );
return true;
}
/** Consumes all elements via a functor.
*
* Sequentially pops all elements and applies the functor to each.
*
* \returns the number of elements consumed.
*
* \note Thread-safe and wait-free when the functor is thread-safe and
* non-blocking. Only one consumer thread should call this.
*/
template < typename Functor >
size_t consume_all( Functor&& f )
{
size_t element_count = 0;
while ( consume_one( f ) )
element_count += 1;
return element_count;
}
private:
#ifndef BOOST_DOXYGEN_INVOKED
atomic< handle_type > head_ {};
alignas( detail::cacheline_bytes ) handle_type tail_ {};
pool_t pool;
#endif
};
}} // namespace boost::lockfree
#if ( defined( BOOST_INTEL ) && ( BOOST_INTEL_CXX_VERSION > 1000 ) ) || defined( _MSC_VER )
# pragma warning( pop )
#endif
#endif /* BOOST_LOCKFREE_MPSC_QUEUE_HPP_INCLUDED */
+41
View File
@@ -26,6 +26,9 @@ struct allocator;
struct fixed_sized;
struct capacity;
struct allow_multiple_reads;
struct freelist;
struct single_producer;
struct single_consumer;
} // namespace tag
template < bool IsFixedSized >
@@ -45,6 +48,20 @@ struct allow_multiple_reads :
boost::parameter::template_keyword< tag::allow_multiple_reads, std::integral_constant< bool, AllowMultipleReads > >
{};
template < bool UseFreelist >
struct freelist : boost::parameter::template_keyword< tag::freelist, std::integral_constant< bool, UseFreelist > >
{};
template < bool IsSingleProducer >
struct single_producer :
boost::parameter::template_keyword< tag::single_producer, std::integral_constant< bool, IsSingleProducer > >
{};
template < bool IsSingleConsumer >
struct single_consumer :
boost::parameter::template_keyword< tag::single_consumer, std::integral_constant< bool, IsSingleConsumer > >
{};
#else
/** Configures a data structure as \b fixed-sized.
@@ -77,6 +94,30 @@ struct allocator;
template < bool AllowMultipleReads >
struct allow_multiple_reads;
/** Configures the mpmc_queue to use a freelist.
*
* The mpmc_queue can be configured to use a freelist for memory management.
*
* */
template < bool UseFreelist >
struct freelist;
/** Configures a data structure for \b single-producer usage.
*
* When set to true, the push operation does not use a CAS and is
* wait-free. Only one thread may call push concurrently.
* */
template < bool IsSingleProducer >
struct single_producer;
/** Configures a data structure for \b single-consumer usage.
*
* When set to true, the pop operation does not use a CAS and is
* wait-free. Only one thread may call pop concurrently.
* */
template < bool IsSingleConsumer >
struct single_consumer;
#endif
}} // namespace boost::lockfree
+1 -3
View File
@@ -120,9 +120,7 @@ private:
next( tagged_node_handle( null_handle, next_tag ) )
{}
template < typename TagT >
node( TagT /*next_tag*/ )
{}
node() = delete;
alignas( detail::cacheline_bytes ) atomic< tagged_node_handle > next;
T data;
+2
View File
@@ -93,6 +93,8 @@ private:
v( std::forward< T >( val ) )
{}
node() = delete;
typedef typename detail::select_tagged_handle< node, node_based >::handle_type handle_t;
handle_t next;
+6
View File
@@ -22,6 +22,12 @@ endif()
set(Tests
destructor_test
freelist_test
bounded_ticket_queue_comprehensive_stress_test
bounded_ticket_queue_stress_test
bounded_ticket_queue_test
mpsc_weak_queue_comprehensive_stress_test
mpsc_weak_queue_stress_test
mpsc_weak_queue_test
queue_bounded_stress_test
queue_comprehensive_stress_test
queue_fixedsize_stress_test
@@ -0,0 +1,53 @@
// Copyright (C) 2026 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/lockfree/bounded_ticket_queue.hpp>
#define BOOST_TEST_MAIN
#ifdef BOOST_LOCKFREE_INCLUDE_TESTS
# include <boost/test/included/unit_test.hpp>
#else
# include <boost/test/unit_test.hpp>
#endif
#include "test_common.hpp"
using namespace boost::lockfree;
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_comprehensive_mpmc_1c )
{
comprehensive_stress_tester< 10, 1 > tester;
bounded_ticket_queue< int > q( 8192 );
tester.run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_comprehensive_mpmc_4c )
{
comprehensive_stress_tester< 10, 4 > tester;
bounded_ticket_queue< int > q( 8192 );
tester.run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_comprehensive_mpsc )
{
comprehensive_stress_tester< 10, 1 > tester;
bounded_ticket_queue< int, single_consumer< true > > q( 8192 );
tester.run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_comprehensive_spmc )
{
comprehensive_stress_tester< 1, 4 > tester;
bounded_ticket_queue< int, single_producer< true > > q( 8192 );
tester.run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_comprehensive_spsc )
{
comprehensive_stress_tester< 1, 1 > tester;
bounded_ticket_queue< int, single_producer< true >, single_consumer< true > > q( 8192 );
tester.run( q );
}
+205
View File
@@ -0,0 +1,205 @@
// Copyright (C) 2026 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/lockfree/bounded_ticket_queue.hpp>
#define BOOST_TEST_MAIN
#ifdef BOOST_LOCKFREE_INCLUDE_TESTS
# include <boost/test/included/unit_test.hpp>
#else
# include <boost/test/unit_test.hpp>
#endif
#include "test_common.hpp"
#include <atomic>
#include <cassert>
#include <iostream>
#include <memory>
#ifdef __VXWORKS__
# include <thread>
#endif
#include <boost/test/test_tools.hpp>
#include <boost/thread/thread.hpp>
namespace impl {
struct bounded_ticket_queue_stress_tester
{
static const unsigned int buckets = 1 << 13;
#ifndef BOOST_LOCKFREE_STRESS_TEST
static const long node_count = 5000;
#else
static const long node_count = 5000000;
#endif
const int writer_threads;
const int reader_threads;
std::atomic< int > writers_finished;
static_hashed_set< long, buckets > data;
static_hashed_set< long, buckets > dequeued;
std::atomic< int > push_count, pop_count;
explicit bounded_ticket_queue_stress_tester( int writers, int readers ) :
writer_threads( writers ),
reader_threads( readers ),
push_count( 0 ),
pop_count( 0 )
{}
template < typename queue >
void add_items( queue& q )
{
for ( long i = 0; i != node_count; ++i ) {
long id = generate_id< long >();
bool inserted = data.insert( id );
assert( inserted );
(void)inserted;
while ( q.push( id ) == false )
;
++push_count;
}
writers_finished += 1;
}
template < typename queue >
bool consume_element( queue& q )
{
long id;
bool ret = q.pop( id );
if ( !ret )
return false;
bool erased = data.erase( id );
bool inserted = dequeued.insert( id );
(void)erased;
(void)inserted;
assert( erased );
assert( inserted );
++pop_count;
return true;
}
template < typename queue >
void get_items( queue& q )
{
for ( ;; ) {
bool received_element = consume_element( q );
if ( received_element )
continue;
if ( writers_finished.load() == writer_threads )
break;
#ifdef __VXWORKS__
std::this_thread::yield();
#endif
}
while ( consume_element( q ) )
;
}
template < typename queue >
void run( queue& q )
{
BOOST_WARN( q.is_lock_free() );
writers_finished.store( 0 );
boost::thread_group writer;
boost::thread_group reader;
BOOST_TEST_REQUIRE( q.empty() );
for ( int i = 0; i != reader_threads; ++i )
reader.create_thread( [ & ] {
get_items( q );
} );
for ( int i = 0; i != writer_threads; ++i )
writer.create_thread( [ & ] {
add_items( q );
} );
std::cout << "threads created" << std::endl;
writer.join_all();
std::cout << "writer threads joined, waiting for readers" << std::endl;
reader.join_all();
std::cout << "reader threads joined" << std::endl;
BOOST_TEST_REQUIRE( data.count_nodes() == (size_t)0 );
BOOST_TEST_REQUIRE( q.empty() );
BOOST_TEST_REQUIRE( push_count == pop_count );
BOOST_TEST_REQUIRE( push_count == writer_threads * node_count );
}
};
} // namespace impl
using impl::bounded_ticket_queue_stress_tester;
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_four_writers_one_reader )
{
auto tester = std::make_unique< bounded_ticket_queue_stress_tester >( 4, 1 );
boost::lockfree::bounded_ticket_queue< long > q( 4096 );
tester->run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_four_writers_four_readers )
{
auto tester = std::make_unique< bounded_ticket_queue_stress_tester >( 4, 4 );
boost::lockfree::bounded_ticket_queue< long > q( 4096 );
tester->run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_one_writer_four_readers )
{
auto tester = std::make_unique< bounded_ticket_queue_stress_tester >( 1, 4 );
boost::lockfree::bounded_ticket_queue< long > q( 4096 );
tester->run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_one_writer_one_reader_mpsc )
{
auto tester = std::make_unique< bounded_ticket_queue_stress_tester >( 4, 1 );
boost::lockfree::bounded_ticket_queue< long, boost::lockfree::single_consumer< true > > q( 4096 );
tester->run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_one_writer_one_reader_spmc )
{
auto tester = std::make_unique< bounded_ticket_queue_stress_tester >( 1, 1 );
boost::lockfree::bounded_ticket_queue< long, boost::lockfree::single_producer< true > > q( 4096 );
tester->run( q );
}
BOOST_AUTO_TEST_CASE( bounded_ticket_queue_one_writer_one_reader_spsc )
{
auto tester = std::make_unique< bounded_ticket_queue_stress_tester >( 1, 1 );
boost::lockfree::
bounded_ticket_queue< long, boost::lockfree::single_producer< true >, boost::lockfree::single_consumer< true > >
q( 4096 );
tester->run( q );
}
+869
View File
@@ -0,0 +1,869 @@
// Copyright (C) 2026 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/lockfree/bounded_ticket_queue.hpp>
#define BOOST_TEST_MAIN
#ifdef BOOST_LOCKFREE_INCLUDE_TESTS
# include <boost/test/included/unit_test.hpp>
#else
# include <boost/test/unit_test.hpp>
#endif
#include <memory>
using namespace boost::lockfree;
// ---- MPMC (multi-producer, multi-consumer, both false) -----
BOOST_AUTO_TEST_CASE( mpmc_simple_test )
{
bounded_ticket_queue< int > f( 64 );
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_simple_test_capacity )
{
bounded_ticket_queue< int, capacity< 64 > > f;
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_exhausted )
{
bounded_ticket_queue< int > f( 4 );
BOOST_TEST_REQUIRE( f.push( 1 ) );
BOOST_TEST_REQUIRE( f.push( 2 ) );
BOOST_TEST_REQUIRE( f.push( 3 ) );
BOOST_TEST_REQUIRE( f.push( 4 ) );
BOOST_TEST_REQUIRE( !f.push( 5 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 1 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 2 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 3 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 4 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_exhausted_capacity )
{
bounded_ticket_queue< int, capacity< 4 > > f;
BOOST_TEST_REQUIRE( f.push( 1 ) );
BOOST_TEST_REQUIRE( f.push( 2 ) );
BOOST_TEST_REQUIRE( f.push( 3 ) );
BOOST_TEST_REQUIRE( f.push( 4 ) );
BOOST_TEST_REQUIRE( !f.push( 5 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 1 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 2 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 3 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 4 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_consume_one_test )
{
bounded_ticket_queue< int > f( 64 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
bool success1 = f.consume_one( []( int i ) {
BOOST_TEST_REQUIRE( i == 1 );
} );
bool success2 = f.consume_one( []( int i ) {
BOOST_TEST_REQUIRE( i == 2 );
} );
BOOST_TEST_REQUIRE( success1 );
BOOST_TEST_REQUIRE( success2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_consume_all_test )
{
bounded_ticket_queue< int > f( 64 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
size_t consumed = f.consume_all( []( int i ) {} );
BOOST_TEST_REQUIRE( consumed == 2u );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_convert_pop_test )
{
bounded_ticket_queue< int* > f( 128 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( new int( 1 ) );
f.push( new int( 2 ) );
f.push( new int( 3 ) );
f.push( new int( 4 ) );
{
int* i1;
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( *i1 == 1 );
delete i1;
}
{
boost::shared_ptr< int > i2;
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( *i2 == 2 );
}
{
std::unique_ptr< int > i3;
BOOST_TEST_REQUIRE( f.pop( i3 ) );
BOOST_TEST_REQUIRE( *i3 == 3 );
}
{
std::shared_ptr< int > i4;
BOOST_TEST_REQUIRE( f.pop( i4 ) );
BOOST_TEST_REQUIRE( *i4 == 4 );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_with_allocator )
{
using allocator_type = std::allocator< char >;
using queue_t = bounded_ticket_queue< char, allocator< allocator_type > >;
auto allocator = queue_t::allocator {};
{
queue_t q_with_size_and_allocator {
16,
allocator,
};
}
{
queue_t q_with_size_and_allocator {
16,
allocator_type {},
};
}
}
BOOST_AUTO_TEST_CASE( mpmc_move_semantics )
{
bounded_ticket_queue< int, capacity< 128 > > q;
q.push( 0 );
q.push( 1 );
auto two = 2;
q.push( std::move( two ) );
int out;
BOOST_TEST_REQUIRE( q.pop( out ) );
BOOST_TEST_REQUIRE( out == 0 );
q.consume_one( []( int one ) {
BOOST_TEST_REQUIRE( one == 1 );
} );
q.consume_all( []( int ) {} );
}
#if !defined( BOOST_NO_CXX17_HDR_OPTIONAL )
BOOST_AUTO_TEST_CASE( mpmc_uses_optional )
{
bounded_ticket_queue< int > stk( 8 );
bool pop_to_nullopt = stk.pop( uses_optional ) == std::nullopt;
BOOST_TEST_REQUIRE( pop_to_nullopt );
stk.push( 53 );
bool pop_to_optional = stk.pop( uses_optional ) == 53;
BOOST_TEST_REQUIRE( pop_to_optional );
}
BOOST_AUTO_TEST_CASE( mpmc_uses_optional_capacity )
{
bounded_ticket_queue< int, capacity< 64 > > q;
bool pop_to_nullopt = q.pop( uses_optional ) == std::nullopt;
BOOST_TEST_REQUIRE( pop_to_nullopt );
q.push( 53 );
bool pop_to_optional = q.pop( uses_optional ) == 53;
BOOST_TEST_REQUIRE( pop_to_optional );
}
#endif
BOOST_AUTO_TEST_CASE( mpmc_empty_pop_test )
{
bounded_ticket_queue< int > f( 64 );
int out = 0xDEAD;
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( !f.consume_one( []( int ) {} ) );
BOOST_TEST_REQUIRE( f.consume_all( []( int ) {} ) == 0u );
}
BOOST_AUTO_TEST_CASE( mpmc_push_pop_many )
{
bounded_ticket_queue< int > f( 128 );
for ( int i = 0; i < 100; ++i )
BOOST_TEST_REQUIRE( f.push( i ) );
for ( int i = 0; i < 100; ++i ) {
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == i );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_push_pop_many_capacity )
{
bounded_ticket_queue< int, capacity< 128 > > f;
for ( int i = 0; i < 100; ++i )
BOOST_TEST_REQUIRE( f.push( i ) );
for ( int i = 0; i < 100; ++i ) {
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == i );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpmc_move_only_types )
{
bounded_ticket_queue< std::unique_ptr< int >, capacity< 128 > > q;
q.push( std::make_unique< int >( 0 ) );
q.push( std::make_unique< int >( 1 ) );
auto two = std::make_unique< int >( 2 );
q.push( std::move( two ) );
std::unique_ptr< int > out;
BOOST_TEST_REQUIRE( q.pop( out ) );
BOOST_TEST_REQUIRE( *out == 0 );
q.consume_one( []( std::unique_ptr< int > one ) {
BOOST_TEST_REQUIRE( *one == 1 );
} );
q.consume_all( []( std::unique_ptr< int > ) {} );
}
BOOST_AUTO_TEST_CASE( mpmc_wrap_around )
{
bounded_ticket_queue< int > f( 4 );
BOOST_TEST_REQUIRE( f.push( 10 ) );
BOOST_TEST_REQUIRE( f.push( 20 ) );
BOOST_TEST_REQUIRE( f.push( 30 ) );
BOOST_TEST_REQUIRE( f.push( 40 ) );
BOOST_TEST_REQUIRE( !f.push( 50 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 10 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 20 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 30 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 40 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
BOOST_TEST_REQUIRE( f.push( 50 ) );
BOOST_TEST_REQUIRE( f.push( 60 ) );
BOOST_TEST_REQUIRE( f.push( 70 ) );
BOOST_TEST_REQUIRE( f.push( 80 ) );
BOOST_TEST_REQUIRE( !f.push( 90 ) );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 50 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 60 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 70 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 80 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
// ---- MPSC (single_consumer<true>) ----
BOOST_AUTO_TEST_CASE( mpsc_simple_test )
{
bounded_ticket_queue< int, single_consumer< true > > f( 64 );
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_simple_test_capacity )
{
bounded_ticket_queue< int, capacity< 64 >, single_consumer< true > > f;
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_exhausted )
{
bounded_ticket_queue< int, single_consumer< true > > f( 4 );
BOOST_TEST_REQUIRE( f.push( 1 ) );
BOOST_TEST_REQUIRE( f.push( 2 ) );
BOOST_TEST_REQUIRE( f.push( 3 ) );
BOOST_TEST_REQUIRE( f.push( 4 ) );
BOOST_TEST_REQUIRE( !f.push( 5 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 1 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 2 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 3 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 4 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_consume_one_test )
{
bounded_ticket_queue< int, single_consumer< true > > f( 64 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
bool success1 = f.consume_one( []( int i ) {
BOOST_TEST_REQUIRE( i == 1 );
} );
bool success2 = f.consume_one( []( int i ) {
BOOST_TEST_REQUIRE( i == 2 );
} );
BOOST_TEST_REQUIRE( success1 );
BOOST_TEST_REQUIRE( success2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_consume_all_test )
{
bounded_ticket_queue< int, single_consumer< true > > f( 64 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
size_t consumed = f.consume_all( []( int i ) {} );
BOOST_TEST_REQUIRE( consumed == 2u );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_convert_pop_test )
{
bounded_ticket_queue< int*, single_consumer< true > > f( 128 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( new int( 1 ) );
f.push( new int( 2 ) );
f.push( new int( 3 ) );
f.push( new int( 4 ) );
{
int* i1;
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( *i1 == 1 );
delete i1;
}
{
boost::shared_ptr< int > i2;
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( *i2 == 2 );
}
{
std::unique_ptr< int > i3;
BOOST_TEST_REQUIRE( f.pop( i3 ) );
BOOST_TEST_REQUIRE( *i3 == 3 );
}
{
std::shared_ptr< int > i4;
BOOST_TEST_REQUIRE( f.pop( i4 ) );
BOOST_TEST_REQUIRE( *i4 == 4 );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_with_allocator )
{
using queue_t = bounded_ticket_queue< char, allocator< std::allocator< char > >, single_consumer< true > >;
auto allocator = queue_t::allocator {};
{
queue_t q_with_size_and_allocator { 16, allocator };
}
{
queue_t q_with_size_and_allocator { 16, std::allocator< char > {} };
}
}
BOOST_AUTO_TEST_CASE( mpsc_move_semantics )
{
bounded_ticket_queue< int, capacity< 128 >, single_consumer< true > > q;
q.push( 0 );
q.push( 1 );
auto two = 2;
q.push( std::move( two ) );
int out;
BOOST_TEST_REQUIRE( q.pop( out ) );
BOOST_TEST_REQUIRE( out == 0 );
q.consume_one( []( int one ) {
BOOST_TEST_REQUIRE( one == 1 );
} );
q.consume_all( []( int ) {} );
}
BOOST_AUTO_TEST_CASE( mpsc_empty_pop_test )
{
bounded_ticket_queue< int, single_consumer< true > > f( 64 );
int out = 0xDEAD;
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( !f.consume_one( []( int ) {} ) );
BOOST_TEST_REQUIRE( f.consume_all( []( int ) {} ) == 0u );
}
BOOST_AUTO_TEST_CASE( mpsc_push_pop_many )
{
bounded_ticket_queue< int, single_consumer< true > > f( 128 );
for ( int i = 0; i < 100; ++i )
BOOST_TEST_REQUIRE( f.push( i ) );
for ( int i = 0; i < 100; ++i ) {
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == i );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_move_only_types )
{
bounded_ticket_queue< std::unique_ptr< int >, capacity< 128 >, single_consumer< true > > q;
q.push( std::make_unique< int >( 0 ) );
q.push( std::make_unique< int >( 1 ) );
auto two = std::make_unique< int >( 2 );
q.push( std::move( two ) );
std::unique_ptr< int > out;
BOOST_TEST_REQUIRE( q.pop( out ) );
BOOST_TEST_REQUIRE( *out == 0 );
q.consume_one( []( std::unique_ptr< int > one ) {
BOOST_TEST_REQUIRE( *one == 1 );
} );
q.consume_all( []( std::unique_ptr< int > ) {} );
}
BOOST_AUTO_TEST_CASE( mpsc_wrap_around )
{
bounded_ticket_queue< int, single_consumer< true > > f( 4 );
BOOST_TEST_REQUIRE( f.push( 10 ) );
BOOST_TEST_REQUIRE( f.push( 20 ) );
BOOST_TEST_REQUIRE( f.push( 30 ) );
BOOST_TEST_REQUIRE( f.push( 40 ) );
BOOST_TEST_REQUIRE( !f.push( 50 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 10 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 20 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 30 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 40 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
BOOST_TEST_REQUIRE( f.push( 50 ) );
BOOST_TEST_REQUIRE( f.push( 60 ) );
BOOST_TEST_REQUIRE( f.push( 70 ) );
BOOST_TEST_REQUIRE( f.push( 80 ) );
BOOST_TEST_REQUIRE( !f.push( 90 ) );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 50 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 60 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 70 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 80 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
// ---- SPMC (single_producer<true>) ----
BOOST_AUTO_TEST_CASE( spmc_simple_test )
{
bounded_ticket_queue< int, single_producer< true > > f( 64 );
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spmc_exhausted )
{
bounded_ticket_queue< int, single_producer< true > > f( 4 );
BOOST_TEST_REQUIRE( f.push( 1 ) );
BOOST_TEST_REQUIRE( f.push( 2 ) );
BOOST_TEST_REQUIRE( f.push( 3 ) );
BOOST_TEST_REQUIRE( f.push( 4 ) );
BOOST_TEST_REQUIRE( !f.push( 5 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 1 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 2 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 3 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 4 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spmc_consume_all_test )
{
bounded_ticket_queue< int, single_producer< true > > f( 64 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
size_t consumed = f.consume_all( []( int i ) {} );
BOOST_TEST_REQUIRE( consumed == 2u );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spmc_push_pop_many )
{
bounded_ticket_queue< int, single_producer< true > > f( 128 );
for ( int i = 0; i < 100; ++i )
BOOST_TEST_REQUIRE( f.push( i ) );
for ( int i = 0; i < 100; ++i ) {
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == i );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spmc_wrap_around )
{
bounded_ticket_queue< int, single_producer< true > > f( 4 );
BOOST_TEST_REQUIRE( f.push( 10 ) );
BOOST_TEST_REQUIRE( f.push( 20 ) );
BOOST_TEST_REQUIRE( f.push( 30 ) );
BOOST_TEST_REQUIRE( f.push( 40 ) );
BOOST_TEST_REQUIRE( !f.push( 50 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 10 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 20 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 30 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 40 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
BOOST_TEST_REQUIRE( f.push( 50 ) );
BOOST_TEST_REQUIRE( f.push( 60 ) );
BOOST_TEST_REQUIRE( f.push( 70 ) );
BOOST_TEST_REQUIRE( f.push( 80 ) );
BOOST_TEST_REQUIRE( !f.push( 90 ) );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 50 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 60 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 70 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 80 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
// ---- SPSC (single_producer<true>, single_consumer<true>) ----
BOOST_AUTO_TEST_CASE( spsc_simple_test )
{
bounded_ticket_queue< int, single_producer< true >, single_consumer< true > > f( 64 );
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spsc_simple_test_capacity )
{
bounded_ticket_queue< int, capacity< 64 >, single_producer< true >, single_consumer< true > > f;
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spsc_exhausted )
{
bounded_ticket_queue< int, single_producer< true >, single_consumer< true > > f( 4 );
BOOST_TEST_REQUIRE( f.push( 1 ) );
BOOST_TEST_REQUIRE( f.push( 2 ) );
BOOST_TEST_REQUIRE( f.push( 3 ) );
BOOST_TEST_REQUIRE( f.push( 4 ) );
BOOST_TEST_REQUIRE( !f.push( 5 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 1 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 2 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 3 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 4 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spsc_consume_all_test )
{
bounded_ticket_queue< int, single_producer< true >, single_consumer< true > > f( 64 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
size_t consumed = f.consume_all( []( int i ) {} );
BOOST_TEST_REQUIRE( consumed == 2u );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spsc_push_pop_many )
{
bounded_ticket_queue< int, single_producer< true >, single_consumer< true > > f( 128 );
for ( int i = 0; i < 100; ++i )
BOOST_TEST_REQUIRE( f.push( i ) );
for ( int i = 0; i < 100; ++i ) {
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == i );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( spsc_wrap_around )
{
bounded_ticket_queue< int, single_producer< true >, single_consumer< true > > f( 4 );
BOOST_TEST_REQUIRE( f.push( 10 ) );
BOOST_TEST_REQUIRE( f.push( 20 ) );
BOOST_TEST_REQUIRE( f.push( 30 ) );
BOOST_TEST_REQUIRE( f.push( 40 ) );
BOOST_TEST_REQUIRE( !f.push( 50 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 10 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 20 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 30 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 40 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
BOOST_TEST_REQUIRE( f.push( 50 ) );
BOOST_TEST_REQUIRE( f.push( 60 ) );
BOOST_TEST_REQUIRE( f.push( 70 ) );
BOOST_TEST_REQUIRE( f.push( 80 ) );
BOOST_TEST_REQUIRE( !f.push( 90 ) );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 50 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 60 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 70 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 80 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
@@ -0,0 +1,29 @@
// Copyright (C) 2026 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/lockfree/mpsc_weak_queue.hpp>
#define BOOST_TEST_MAIN
#ifdef BOOST_LOCKFREE_INCLUDE_TESTS
# include <boost/test/included/unit_test.hpp>
#else
# include <boost/test/unit_test.hpp>
#endif
#include "test_common.hpp"
namespace {
using comprehensive_stress_tester_mpsc = comprehensive_stress_tester< 10, 1 >;
} // namespace
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_comprehensive_stress_1_consumer )
{
comprehensive_stress_tester_mpsc tester;
boost::lockfree::mpsc_weak_queue< int > q( 128 );
tester.run( q );
}
+193
View File
@@ -0,0 +1,193 @@
// Copyright (C) 2024 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/lockfree/mpsc_weak_queue.hpp>
#define BOOST_TEST_MAIN
#ifdef BOOST_LOCKFREE_INCLUDE_TESTS
# include <boost/test/included/unit_test.hpp>
#else
# include <boost/test/unit_test.hpp>
#endif
#include "test_common.hpp"
#include "test_helpers.hpp"
#include <array>
#include <atomic>
#include <cassert>
#include <iostream>
#ifdef __VXWORKS__
# include <thread>
#endif
#include <boost/test/test_tools.hpp>
#include <boost/thread/thread.hpp>
namespace impl {
template < bool Bounded = false >
struct mpsc_weak_queue_stress_tester
{
static const unsigned int buckets = 1 << 13;
#ifndef BOOST_LOCKFREE_STRESS_TEST
static const long node_count = 5000;
#else
static const long node_count = 5000000;
#endif
const int writer_threads;
std::atomic< int > writers_finished;
static_hashed_set< long, buckets > data;
static_hashed_set< long, buckets > dequeued;
std::array< std::set< long >, buckets > returned;
std::atomic< int > push_count, pop_count;
explicit mpsc_weak_queue_stress_tester( int writer ) :
writer_threads( writer ),
push_count( 0 ),
pop_count( 0 )
{}
template < typename queue >
void add_items( queue& q )
{
for ( long i = 0; i != node_count; ++i ) {
long id = generate_id< long >();
bool inserted = data.insert( id );
assert( inserted );
(void)inserted;
if ( Bounded )
while ( q.bounded_push( id ) == false ) {
#ifdef __VXWORKS__
std::this_thread::yield();
#endif
}
else
while ( q.push( id ) == false ) {
#ifdef __VXWORKS__
std::this_thread::yield();
#endif
}
++push_count;
}
writers_finished += 1;
}
std::atomic< bool > running;
template < typename queue >
bool consume_element( queue& q )
{
long id;
bool ret = q.pop( id );
if ( !ret )
return false;
bool erased = data.erase( id );
bool inserted = dequeued.insert( id );
(void)erased;
(void)inserted;
assert( erased );
assert( inserted );
++pop_count;
return true;
}
template < typename queue >
void get_items( queue& q )
{
for ( ;; ) {
bool received_element = consume_element( q );
if ( received_element )
continue;
if ( writers_finished.load() == writer_threads )
break;
#ifdef __VXWORKS__
std::this_thread::yield();
#endif
}
while ( consume_element( q ) )
;
}
template < typename queue >
void run( queue& q )
{
BOOST_WARN( q.is_lock_free() );
writers_finished.store( 0 );
boost::thread_group writer;
boost::thread reader;
BOOST_TEST_REQUIRE( q.empty() );
reader = boost::thread( [ & ] {
get_items( q );
} );
for ( int i = 0; i != writer_threads; ++i )
writer.create_thread( [ & ] {
add_items( q );
} );
std::cout << "threads created" << std::endl;
writer.join_all();
std::cout << "writer threads joined, waiting for readers" << std::endl;
reader.join();
std::cout << "reader thread joined" << std::endl;
BOOST_TEST_REQUIRE( data.count_nodes() == (size_t)0 );
BOOST_TEST_REQUIRE( q.empty() );
BOOST_TEST_REQUIRE( push_count == pop_count );
BOOST_TEST_REQUIRE( push_count == writer_threads * node_count );
}
};
} // namespace impl
using impl::mpsc_weak_queue_stress_tester;
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_test_unbounded )
{
typedef mpsc_weak_queue_stress_tester< false > tester_type;
std::unique_ptr< tester_type > tester( new tester_type( 4 ) );
boost::lockfree::mpsc_weak_queue< long > q( 128 );
tester->run( q );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_test_unbounded_single_writer )
{
typedef mpsc_weak_queue_stress_tester< false > tester_type;
std::unique_ptr< tester_type > tester( new tester_type( 1 ) );
boost::lockfree::mpsc_weak_queue< long > q( 128 );
tester->run( q );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_test_bounded )
{
typedef mpsc_weak_queue_stress_tester< true > tester_type;
std::unique_ptr< tester_type > tester( new tester_type( 4 ) );
boost::lockfree::mpsc_weak_queue< long > q( 128 );
tester->run( q );
}
+410
View File
@@ -0,0 +1,410 @@
// Copyright (C) 2024 Tim Blechmann
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/lockfree/lockfree_forward.hpp>
#include <boost/lockfree/mpsc_weak_queue.hpp>
#define BOOST_TEST_MAIN
#ifdef BOOST_LOCKFREE_INCLUDE_TESTS
# include <boost/test/included/unit_test.hpp>
#else
# include <boost/test/unit_test.hpp>
#endif
#include <memory>
using namespace boost::lockfree;
BOOST_AUTO_TEST_CASE( simple_mpsc_weak_queue_test )
{
mpsc_weak_queue< int > f( 64 );
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( simple_mpsc_weak_queue_test_capacity )
{
mpsc_weak_queue< int, capacity< 64 > > f;
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( unsafe_mpsc_weak_queue_test )
{
mpsc_weak_queue< int > f( 64 );
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
int i1( 0 ), i2( 0 );
f.unsynchronized_push( 1 );
f.unsynchronized_push( 2 );
BOOST_TEST_REQUIRE( f.unsynchronized_pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 1 );
BOOST_TEST_REQUIRE( f.unsynchronized_pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_consume_one_test )
{
mpsc_weak_queue< int > f( 64 );
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
bool success1 = f.consume_one( []( int i ) {
BOOST_TEST_REQUIRE( i == 1 );
} );
bool success2 = f.consume_one( []( int i ) mutable {
BOOST_TEST_REQUIRE( i == 2 );
} );
BOOST_TEST_REQUIRE( success1 );
BOOST_TEST_REQUIRE( success2 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_consume_all_test )
{
mpsc_weak_queue< int > f( 64 );
BOOST_TEST_WARN( f.is_lock_free() );
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
size_t consumed = f.consume_all( []( int i ) {} );
BOOST_TEST_REQUIRE( consumed == 2u );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_convert_pop_test )
{
mpsc_weak_queue< int* > f( 128 );
BOOST_TEST_REQUIRE( f.empty() );
f.push( new int( 1 ) );
f.push( new int( 2 ) );
f.push( new int( 3 ) );
f.push( new int( 4 ) );
{
int* i1;
BOOST_TEST_REQUIRE( f.pop( i1 ) );
BOOST_TEST_REQUIRE( *i1 == 1 );
delete i1;
}
{
boost::shared_ptr< int > i2;
BOOST_TEST_REQUIRE( f.pop( i2 ) );
BOOST_TEST_REQUIRE( *i2 == 2 );
}
{
std::unique_ptr< int > i3;
BOOST_TEST_REQUIRE( f.pop( i3 ) );
BOOST_TEST_REQUIRE( *i3 == 3 );
}
{
std::shared_ptr< int > i4;
BOOST_TEST_REQUIRE( f.pop( i4 ) );
BOOST_TEST_REQUIRE( *i4 == 4 );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( reserve_test )
{
typedef boost::lockfree::mpsc_weak_queue< void* > memory_queue;
memory_queue ms( 1 );
ms.reserve( 1 );
ms.reserve_unsafe( 1 );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_with_allocator )
{
using allocator_type = std::allocator< char >;
using queue_t = boost::lockfree::mpsc_weak_queue< char, boost::lockfree::allocator< allocator_type > >;
using queue_with_capacity_t = boost::lockfree::
mpsc_weak_queue< char, boost::lockfree::allocator< allocator_type >, boost::lockfree::capacity< 16 > >;
auto allocator = queue_t::allocator {};
{
queue_with_capacity_t q_with_allocator {
allocator,
};
queue_t q_with_size_and_allocator {
5,
allocator,
};
}
{
queue_with_capacity_t q_with_allocator {
allocator_type {},
};
queue_t q_with_size_and_allocator {
5,
allocator_type {},
};
}
}
BOOST_AUTO_TEST_CASE( move_semantics )
{
boost::lockfree::mpsc_weak_queue< int, boost::lockfree::capacity< 128 > > stk;
stk.push( 0 );
stk.push( 1 );
auto two = 2;
stk.push( std::move( two ) );
int out;
BOOST_TEST_REQUIRE( stk.pop( out ) );
BOOST_TEST_REQUIRE( out == 0 );
stk.consume_one( []( int one ) {
BOOST_TEST_REQUIRE( one == 1 );
} );
stk.consume_all( []( int ) {} );
}
#if !defined( BOOST_NO_CXX17_HDR_OPTIONAL )
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_uses_optional )
{
boost::lockfree::mpsc_weak_queue< int > stk( 5 );
bool pop_to_nullopt = stk.pop( boost::lockfree::uses_optional ) == std::nullopt;
BOOST_TEST_REQUIRE( pop_to_nullopt );
stk.push( 53 );
bool pop_to_optional = stk.pop( boost::lockfree::uses_optional ) == 53;
BOOST_TEST_REQUIRE( pop_to_optional );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_uses_optional_capacity )
{
boost::lockfree::mpsc_weak_queue< int, boost::lockfree::capacity< 64 > > stk;
bool pop_to_nullopt = stk.pop( boost::lockfree::uses_optional ) == std::nullopt;
BOOST_TEST_REQUIRE( pop_to_nullopt );
stk.push( 53 );
bool pop_to_optional = stk.pop( boost::lockfree::uses_optional ) == 53;
BOOST_TEST_REQUIRE( pop_to_optional );
}
#endif
BOOST_AUTO_TEST_CASE( fixed_size_mpsc_weak_queue_test_exhausted )
{
mpsc_weak_queue< int, capacity< 2 >, freelist< true > > f;
BOOST_TEST_REQUIRE( f.push( 1 ) );
BOOST_TEST_REQUIRE( f.push( 2 ) );
BOOST_TEST_REQUIRE( !f.push( 3 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 1 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 2 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( bounded_mpsc_weak_queue_test_exhausted )
{
mpsc_weak_queue< int, freelist< true > > f( 2 );
BOOST_TEST_REQUIRE( f.bounded_push( 1 ) );
BOOST_TEST_REQUIRE( f.bounded_push( 2 ) );
BOOST_TEST_REQUIRE( !f.bounded_push( 3 ) );
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 1 );
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == 2 );
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_unsynchronized_push_const_ref )
{
mpsc_weak_queue< int > f( 64 );
BOOST_TEST_REQUIRE( f.empty() );
const int a = 42;
const int b = 43;
f.unsynchronized_push( a );
f.unsynchronized_push( b );
int i1( 0 ), i2( 0 );
BOOST_TEST_REQUIRE( f.unsynchronized_pop( i1 ) );
BOOST_TEST_REQUIRE( i1 == 42 );
BOOST_TEST_REQUIRE( f.unsynchronized_pop( i2 ) );
BOOST_TEST_REQUIRE( i2 == 43 );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_consume_one_capacity_test )
{
mpsc_weak_queue< int, capacity< 64 > > f;
BOOST_TEST_REQUIRE( f.empty() );
f.push( 10 );
f.push( 20 );
bool success1 = f.consume_one( []( int i ) {
BOOST_TEST_REQUIRE( i == 10 );
} );
bool success2 = f.consume_one( []( int i ) {
BOOST_TEST_REQUIRE( i == 20 );
} );
BOOST_TEST_REQUIRE( success1 );
BOOST_TEST_REQUIRE( success2 );
BOOST_TEST_REQUIRE( !f.consume_one( []( int ) {} ) );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_consume_all_capacity_test )
{
mpsc_weak_queue< int, capacity< 64 > > f;
BOOST_TEST_REQUIRE( f.empty() );
f.push( 1 );
f.push( 2 );
f.push( 3 );
size_t consumed = f.consume_all( []( int ) {} );
BOOST_TEST_REQUIRE( consumed == 3u );
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_empty_pop_test )
{
mpsc_weak_queue< int > f( 64 );
int out = 0xDEAD;
BOOST_TEST_REQUIRE( !f.pop( out ) );
BOOST_TEST_REQUIRE( !f.unsynchronized_pop( out ) );
BOOST_TEST_REQUIRE( !f.consume_one( []( int ) {} ) );
BOOST_TEST_REQUIRE( f.consume_all( []( int ) {} ) == 0u );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_push_pop_many )
{
mpsc_weak_queue< int > f( 64 );
for ( int i = 0; i < 100; ++i )
BOOST_TEST_REQUIRE( f.push( i ) );
for ( int i = 0; i < 100; ++i ) {
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == i );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( mpsc_weak_queue_push_pop_many_capacity )
{
mpsc_weak_queue< int, capacity< 128 > > f;
for ( int i = 0; i < 100; ++i )
BOOST_TEST_REQUIRE( f.push( i ) );
for ( int i = 0; i < 100; ++i ) {
int out;
BOOST_TEST_REQUIRE( f.pop( out ) );
BOOST_TEST_REQUIRE( out == i );
}
BOOST_TEST_REQUIRE( f.empty() );
}
BOOST_AUTO_TEST_CASE( move_only_types )
{
boost::lockfree::mpsc_weak_queue< std::unique_ptr< int >, boost::lockfree::capacity< 128 > > stk;
stk.push( std::make_unique< int >( 0 ) );
stk.push( std::make_unique< int >( 1 ) );
auto two = std::make_unique< int >( 2 );
stk.push( std::move( two ) );
std::unique_ptr< int > out;
BOOST_TEST_REQUIRE( stk.pop( out ) );
BOOST_TEST_REQUIRE( *out == 0 );
stk.consume_one( []( std::unique_ptr< int > one ) {
BOOST_TEST_REQUIRE( *one == 1 );
} );
stk.consume_all( []( std::unique_ptr< int > ) {} );
}