mirror of
https://github.com/boostorg/lockfree.git
synced 2026-07-21 13:23:29 +00:00
introduce mpsc_weak_queue / bounded_ticket_queue
Add Dmitry Vyukov's queues
This commit is contained in:
@@ -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
@@ -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 267–275, 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<U> 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
|
||||
|
||||
@@ -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<U> 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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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 > ) {} );
|
||||
}
|
||||
Reference in New Issue
Block a user