Add ReaderWriterQueue and update ConcurrentQueue

This commit is contained in:
Paul Hollinsky
2020-03-09 13:38:14 -04:00
parent 9ac3fd56bd
commit 42780dc610
1629 changed files with 306008 additions and 868 deletions
+204 -162
View File
@@ -1,4 +1,4 @@
// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue.
// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue.
// An overview, including benchmark results, is provided here:
// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++
// The full design is also described in excruciating detail at:
@@ -30,12 +30,6 @@
#pragma once
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4127) // ICS: Warnings generated by this file
#pragma warning(disable:4706)
#endif
#if defined(__GNUC__)
// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and
// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings
@@ -152,10 +146,21 @@ namespace moodycamel { namespace details {
typedef std::uintptr_t thread_id_t;
static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr
static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned.
static inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast<thread_id_t>(&x); }
inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast<thread_id_t>(&x); }
} }
#endif
// Constexpr if
#ifndef MOODYCAMEL_CONSTEXPR_IF
#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L
#define MOODYCAMEL_CONSTEXPR_IF if constexpr
#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]]
#else
#define MOODYCAMEL_CONSTEXPR_IF if
#define MOODYCAMEL_MAYBE_UNUSED
#endif
#endif
// Exceptions
#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED
#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__))
@@ -168,8 +173,8 @@ namespace moodycamel { namespace details {
#define MOODYCAMEL_RETHROW throw
#define MOODYCAMEL_THROW(expr) throw (expr)
#else
#define MOODYCAMEL_TRY if (true)
#define MOODYCAMEL_CATCH(...) else if (false)
#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true)
#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false)
#define MOODYCAMEL_RETHROW
#define MOODYCAMEL_THROW(expr)
#endif
@@ -220,6 +225,19 @@ namespace moodycamel { namespace details {
#endif
#endif
#ifndef MOODYCAMEL_ALIGNAS
// VS2013 doesn't support alignas or alignof
#if defined(_MSC_VER) && _MSC_VER <= 1800
#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment))
#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj)
#else
#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment)
#define MOODYCAMEL_ALIGNOF(obj) alignof(obj)
#endif
#endif
// Compiler-specific likely/unlikely hints
namespace moodycamel { namespace details {
#if defined(__GNUC__)
@@ -791,7 +809,7 @@ public:
}
// Destroy implicit producer hash tables
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) {
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) {
auto hash = implicitProducerHash.load(std::memory_order_relaxed);
while (hash != nullptr) {
auto prev = hash->prev;
@@ -916,8 +934,8 @@ public:
// Thread-safe.
inline bool enqueue(T const& item)
{
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
return inner_enqueue<CanAlloc>(item);
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
else return inner_enqueue<CanAlloc>(item);
}
// Enqueues a single item (by moving it, if possible).
@@ -927,8 +945,8 @@ public:
// Thread-safe.
inline bool enqueue(T&& item)
{
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
return inner_enqueue<CanAlloc>(std::move(item));
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
else return inner_enqueue<CanAlloc>(std::move(item));
}
// Enqueues a single item (by copying it) using an explicit producer token.
@@ -958,8 +976,8 @@ public:
template<typename It>
bool enqueue_bulk(It itemFirst, size_t count)
{
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
return inner_enqueue_bulk<CanAlloc>(itemFirst, count);
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
else return inner_enqueue_bulk<CanAlloc>(itemFirst, count);
}
// Enqueues several items using an explicit producer token.
@@ -981,8 +999,8 @@ public:
// Thread-safe.
inline bool try_enqueue(T const& item)
{
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
return inner_enqueue<CannotAlloc>(item);
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
else return inner_enqueue<CannotAlloc>(item);
}
// Enqueues a single item (by moving it, if possible).
@@ -992,8 +1010,8 @@ public:
// Thread-safe.
inline bool try_enqueue(T&& item)
{
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
return inner_enqueue<CannotAlloc>(std::move(item));
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
else return inner_enqueue<CannotAlloc>(std::move(item));
}
// Enqueues a single item (by copying it) using an explicit producer token.
@@ -1022,8 +1040,8 @@ public:
template<typename It>
bool try_enqueue_bulk(It itemFirst, size_t count)
{
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
return inner_enqueue_bulk<CannotAlloc>(itemFirst, count);
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
else return inner_enqueue_bulk<CannotAlloc>(itemFirst, count);
}
// Enqueues several items using an explicit producer token.
@@ -1380,7 +1398,7 @@ private:
inline void add(N* node)
{
#if MCDBGQ_NOLOCKFREE_FREELIST
#ifdef MCDBGQ_NOLOCKFREE_FREELIST
debug::DebugLock lock(mutex);
#endif
// We know that the should-be-on-freelist bit is 0 at this point, so it's safe to
@@ -1394,7 +1412,7 @@ private:
inline N* try_get()
{
#if MCDBGQ_NOLOCKFREE_FREELIST
#ifdef MCDBGQ_NOLOCKFREE_FREELIST
debug::DebugLock lock(mutex);
#endif
auto head = freeListHead.load(std::memory_order_acquire);
@@ -1466,7 +1484,7 @@ private:
static const std::uint32_t REFS_MASK = 0x7FFFFFFF;
static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000;
#if MCDBGQ_NOLOCKFREE_FREELIST
#ifdef MCDBGQ_NOLOCKFREE_FREELIST
debug::DebugMutex mutex;
#endif
};
@@ -1483,7 +1501,7 @@ private:
Block()
: next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true)
{
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
owner = nullptr;
#endif
}
@@ -1491,7 +1509,7 @@ private:
template<InnerQueueContext context>
inline bool is_empty() const
{
if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
// Check flags
for (size_t i = 0; i < BLOCK_SIZE; ++i) {
if (!emptyFlags[i].load(std::memory_order_relaxed)) {
@@ -1516,9 +1534,9 @@ private:
// Returns true if the block is now empty (does not apply in explicit context)
template<InnerQueueContext context>
inline bool set_empty(index_t i)
inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i)
{
if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
// Set flag
assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1))].load(std::memory_order_relaxed));
emptyFlags[BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1))].store(true, std::memory_order_release);
@@ -1535,9 +1553,9 @@ private:
// Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0).
// Returns true if the block is now empty (does not apply in explicit context).
template<InnerQueueContext context>
inline bool set_many_empty(index_t i, size_t count)
inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count)
{
if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
// Set flags
std::atomic_thread_fence(std::memory_order_release);
i = BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1)) - count + 1;
@@ -1558,7 +1576,7 @@ private:
template<InnerQueueContext context>
inline void set_all_empty()
{
if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
// Set all flags
for (size_t i = 0; i != BLOCK_SIZE; ++i) {
emptyFlags[i].store(true, std::memory_order_relaxed);
@@ -1573,7 +1591,7 @@ private:
template<InnerQueueContext context>
inline void reset_empty()
{
if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
// Reset flags
for (size_t i = 0; i != BLOCK_SIZE; ++i) {
emptyFlags[i].store(false, std::memory_order_relaxed);
@@ -1589,20 +1607,8 @@ private:
inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast<T const*>(static_cast<void const*>(elements)) + static_cast<size_t>(idx & static_cast<index_t>(BLOCK_SIZE - 1)); }
private:
// IMPORTANT: This must be the first member in Block, so that if T depends on the alignment of
// addresses returned by malloc, that alignment will be preserved. Apparently clang actually
// generates code that uses this assumption for AVX instructions in some cases. Ideally, we
// should also align Block to the alignment of T in case it's higher than malloc's 16-byte
// alignment, but this is hard to do in a cross-platform way. Assert for this case:
static_assert(std::alignment_of<T>::value <= std::alignment_of<details::max_align_t>::value, "The queue does not support super-aligned types at this time");
// Additionally, we need the alignment of Block itself to be a multiple of max_align_t since
// otherwise the appropriate padding will not be added at the end of Block in order to make
// arrays of Blocks all be properly aligned (not just the first one). We use a union to force
// this.
union {
char elements[sizeof(T) * BLOCK_SIZE];
details::max_align_t dummy;
};
static_assert(std::alignment_of<T>::value <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time");
MOODYCAMEL_ALIGNAS(MOODYCAMEL_ALIGNOF(T)) char elements[sizeof(T) * BLOCK_SIZE];
public:
Block* next;
std::atomic<size_t> elementsCompletelyDequeued;
@@ -1613,14 +1619,14 @@ private:
std::atomic<bool> shouldBeOnFreeList;
bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool'
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
void* owner;
#endif
};
static_assert(std::alignment_of<Block>::value >= std::alignment_of<details::max_align_t>::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping");
static_assert(std::alignment_of<Block>::value >= std::alignment_of<T>::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping");
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
public:
struct MemStats;
private:
@@ -1691,7 +1697,7 @@ private:
ConcurrentQueue* parent;
protected:
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
friend struct MemStats;
#endif
};
@@ -1703,8 +1709,8 @@ private:
struct ExplicitProducer : public ProducerBase
{
explicit ExplicitProducer(ConcurrentQueue* parent) :
ProducerBase(parent, true),
explicit ExplicitProducer(ConcurrentQueue* parent_) :
ProducerBase(parent_, true),
blockIndex(nullptr),
pr_blockIndexSlotsUsed(0),
pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1),
@@ -1712,7 +1718,7 @@ private:
pr_blockIndexEntries(nullptr),
pr_blockIndexRaw(nullptr)
{
size_t poolBasedIndexSize = details::ceil_to_pow_2(parent->initialBlockPoolSize) >> 1;
size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1;
if (poolBasedIndexSize > pr_blockIndexSize) {
pr_blockIndexSize = poolBasedIndexSize;
}
@@ -1824,7 +1830,10 @@ private:
// to allocate a new index. Note pr_blockIndexRaw can only be nullptr if
// the initial allocation failed in the constructor.
if (allocMode == CannotAlloc || !new_block_index(pr_blockIndexSlotsUsed)) {
MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) {
return false;
}
else if (!new_block_index(pr_blockIndexSlotsUsed)) {
return false;
}
}
@@ -1834,7 +1843,7 @@ private:
if (newBlock == nullptr) {
return false;
}
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
newBlock->owner = this;
#endif
newBlock->ConcurrentQueue::Block::template reset_empty<explicit_context>();
@@ -1848,8 +1857,8 @@ private:
this->tailBlock = newBlock;
++pr_blockIndexSlotsUsed;
}
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward<U>(element)))) {
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new ((T*)nullptr) T(std::forward<U>(element)))) {
// The constructor may throw. We want the element not to appear in the queue in
// that case (without corrupting the queue):
MOODYCAMEL_TRY {
@@ -1875,7 +1884,7 @@ private:
blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release);
pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1);
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward<U>(element)))) {
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new ((T*)nullptr) T(std::forward<U>(element)))) {
this->tailIndex.store(newTailIndex, std::memory_order_release);
return true;
}
@@ -1968,12 +1977,12 @@ private:
block->ConcurrentQueue::Block::template set_empty<explicit_context>(index);
}
} guard = { block, index };
element = std::move(el);
element = std::move(el); // NOLINT
}
else {
element = std::move(el);
el.~T();
element = std::move(el); // NOLINT
el.~T(); // NOLINT
block->ConcurrentQueue::Block::template set_empty<explicit_context>(index);
}
@@ -2028,7 +2037,14 @@ private:
assert(!details::circular_less_than<index_t>(currentTailIndex, head));
bool full = !details::circular_less_than<index_t>(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max<size_t>::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head));
if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) {
if (allocMode == CannotAlloc || full || !new_block_index(originalBlockIndexSlotsUsed)) {
MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) {
// Failed to allocate, undo changes (but keep injected blocks)
pr_blockIndexFront = originalBlockIndexFront;
pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed;
this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock;
return false;
}
else if (full || !new_block_index(originalBlockIndexSlotsUsed)) {
// Failed to allocate, undo changes (but keep injected blocks)
pr_blockIndexFront = originalBlockIndexFront;
pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed;
@@ -2051,7 +2067,7 @@ private:
return false;
}
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
newBlock->owner = this;
#endif
newBlock->ConcurrentQueue::Block::template set_all_empty<explicit_context>();
@@ -2084,7 +2100,7 @@ private:
block = block->next;
}
if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) {
if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) {
blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release);
}
}
@@ -2103,7 +2119,7 @@ private:
if (details::circular_less_than<index_t>(newTailIndex, stopIndex)) {
stopIndex = newTailIndex;
}
if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) {
if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) {
while (currentTailIndex != stopIndex) {
new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++);
}
@@ -2118,7 +2134,7 @@ private:
// may only define a (noexcept) move constructor, and so calls to the
// cctor will not compile, even if they are in an if branch that will never
// be executed
new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst));
new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst));
++currentTailIndex;
++itemFirst;
}
@@ -2165,7 +2181,7 @@ private:
this->tailBlock = this->tailBlock->next;
}
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst))) && firstAllocatedBlock != nullptr) {
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst))) && firstAllocatedBlock != nullptr) {
blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release);
}
@@ -2337,7 +2353,7 @@ private:
private:
#endif
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
friend struct MemStats;
#endif
};
@@ -2349,8 +2365,8 @@ private:
struct ImplicitProducer : public ProducerBase
{
ImplicitProducer(ConcurrentQueue* parent) :
ProducerBase(parent, false),
ImplicitProducer(ConcurrentQueue* parent_) :
ProducerBase(parent_, false),
nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE),
blockIndex(nullptr)
{
@@ -2424,7 +2440,7 @@ private:
if (!details::circular_less_than<index_t>(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max<size_t>::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) {
return false;
}
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
debug::DebugLock lock(mutex);
#endif
// Find out where we'll be inserting this block in the block index
@@ -2440,12 +2456,12 @@ private:
idxEntry->value.store(nullptr, std::memory_order_relaxed);
return false;
}
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
newBlock->owner = this;
#endif
newBlock->ConcurrentQueue::Block::template reset_empty<implicit_context>();
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward<U>(element)))) {
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new ((T*)nullptr) T(std::forward<U>(element)))) {
// May throw, try to insert now before we publish the fact that we have this new block
MOODYCAMEL_TRY {
new ((*newBlock)[currentTailIndex]) T(std::forward<U>(element));
@@ -2463,7 +2479,7 @@ private:
this->tailBlock = newBlock;
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward<U>(element)))) {
if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new ((T*)nullptr) T(std::forward<U>(element)))) {
this->tailIndex.store(newTailIndex, std::memory_order_release);
return true;
}
@@ -2498,7 +2514,7 @@ private:
auto& el = *((*block)[index]);
if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) {
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
// Note: Acquiring the mutex with every dequeue instead of only when a block
// is released is very sub-optimal, but it is, after all, purely debug code.
debug::DebugLock lock(producer->mutex);
@@ -2518,16 +2534,16 @@ private:
}
}
} guard = { block, index, entry, this->parent };
element = std::move(el);
element = std::move(el); // NOLINT
}
else {
element = std::move(el);
el.~T();
element = std::move(el); // NOLINT
el.~T(); // NOLINT
if (block->ConcurrentQueue::Block::template set_empty<implicit_context>(index)) {
{
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
debug::DebugLock lock(mutex);
#endif
// Add the block back into the global free pool (and remove from block index)
@@ -2568,7 +2584,7 @@ private:
size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1));
index_t currentTailIndex = (startTailIndex - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1);
if (blockBaseDiff > 0) {
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
debug::DebugLock lock(mutex);
#endif
do {
@@ -2602,7 +2618,7 @@ private:
return false;
}
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
newBlock->owner = this;
#endif
newBlock->ConcurrentQueue::Block::template reset_empty<implicit_context>();
@@ -2636,7 +2652,7 @@ private:
if (details::circular_less_than<index_t>(newTailIndex, stopIndex)) {
stopIndex = newTailIndex;
}
if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) {
if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))) {
while (currentTailIndex != stopIndex) {
new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++);
}
@@ -2644,7 +2660,7 @@ private:
else {
MOODYCAMEL_TRY {
while (currentTailIndex != stopIndex) {
new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst));
new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new ((T*)nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst));
++currentTailIndex;
++itemFirst;
}
@@ -2759,7 +2775,7 @@ private:
}
if (block->ConcurrentQueue::Block::template set_many_empty<implicit_context>(blockStartIndex, static_cast<size_t>(endIndex - blockStartIndex))) {
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
debug::DebugLock lock(mutex);
#endif
entry->value.store(nullptr, std::memory_order_relaxed);
@@ -2777,7 +2793,7 @@ private:
}
if (block->ConcurrentQueue::Block::template set_many_empty<implicit_context>(blockStartIndex, static_cast<size_t>(endIndex - blockStartIndex))) {
{
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
debug::DebugLock lock(mutex);
#endif
// Note that the set_many_empty above did a release, meaning that anybody who acquires the block
@@ -2836,7 +2852,10 @@ private:
}
// No room in the old block index, try to allocate another one!
if (allocMode == CannotAlloc || !new_block_index()) {
MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) {
return false;
}
else if (!new_block_index()) {
return false;
}
localBlockIndex = blockIndex.load(std::memory_order_relaxed);
@@ -2863,7 +2882,7 @@ private:
inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const
{
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
debug::DebugLock lock(mutex);
#endif
index &= ~static_cast<index_t>(BLOCK_SIZE - 1);
@@ -2939,10 +2958,10 @@ private:
private:
#endif
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
mutable debug::DebugMutex mutex;
#endif
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
friend struct MemStats;
#endif
};
@@ -2982,7 +3001,7 @@ private:
inline void add_block_to_free_list(Block* block)
{
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
block->owner = nullptr;
#endif
freeList.add(block);
@@ -3016,15 +3035,16 @@ private:
return block;
}
if (canAlloc == CanAlloc) {
MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) {
return create<Block>();
}
return nullptr;
else {
return nullptr;
}
}
#if MCDBGQ_TRACKMEM
#ifdef MCDBGQ_TRACKMEM
public:
struct MemStats {
size_t allocatedBlocks;
@@ -3142,7 +3162,7 @@ private:
ProducerBase* recycle_or_create_producer(bool isExplicit, bool& recycled)
{
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
debug::DebugLock lock(implicitProdMutex);
#endif
// Try to re-use one first
@@ -3249,50 +3269,56 @@ private:
inline void populate_initial_implicit_producer_hash()
{
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return;
implicitProducerHashCount.store(0, std::memory_order_relaxed);
auto hash = &initialImplicitProducerHash;
hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE;
hash->entries = &initialImplicitProducerHashEntries[0];
for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) {
initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed);
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) {
return;
}
else {
implicitProducerHashCount.store(0, std::memory_order_relaxed);
auto hash = &initialImplicitProducerHash;
hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE;
hash->entries = &initialImplicitProducerHashEntries[0];
for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) {
initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed);
}
hash->prev = nullptr;
implicitProducerHash.store(hash, std::memory_order_relaxed);
}
hash->prev = nullptr;
implicitProducerHash.store(hash, std::memory_order_relaxed);
}
void swap_implicit_producer_hashes(ConcurrentQueue& other)
{
if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return;
// Swap (assumes our implicit producer hash is initialized)
initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries);
initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0];
other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0];
details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount);
details::swap_relaxed(implicitProducerHash, other.implicitProducerHash);
if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) {
implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed);
MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) {
return;
}
else {
ImplicitProducerHash* hash;
for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) {
continue;
// Swap (assumes our implicit producer hash is initialized)
initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries);
initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0];
other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0];
details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount);
details::swap_relaxed(implicitProducerHash, other.implicitProducerHash);
if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) {
implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed);
}
hash->prev = &initialImplicitProducerHash;
}
if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) {
other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed);
}
else {
ImplicitProducerHash* hash;
for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) {
continue;
else {
ImplicitProducerHash* hash;
for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) {
continue;
}
hash->prev = &initialImplicitProducerHash;
}
if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) {
other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed);
}
else {
ImplicitProducerHash* hash;
for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) {
continue;
}
hash->prev = &other.initialImplicitProducerHash;
}
hash->prev = &other.initialImplicitProducerHash;
}
}
@@ -3309,7 +3335,7 @@ private:
// Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
debug::DebugLock lock(implicitProdMutex);
#endif
@@ -3317,6 +3343,7 @@ private:
auto hashedId = details::hash_thread_id(id);
auto mainHash = implicitProducerHash.load(std::memory_order_acquire);
assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null)
for (auto hash = mainHash; hash != nullptr; hash = hash->prev) {
// Look for the id in this hash
auto index = hashedId;
@@ -3363,6 +3390,7 @@ private:
// Insert!
auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed);
while (true) {
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) {
// We've acquired the resize lock, try to allocate a bigger hash table.
// Note the acquire fence synchronizes with the release fence at the end of this block, and hence when
@@ -3454,7 +3482,7 @@ private:
details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener);
// Remove from hash
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
debug::DebugLock lock(implicitProdMutex);
#endif
auto hash = implicitProducerHash.load(std::memory_order_acquire);
@@ -3493,55 +3521,73 @@ private:
//////////////////////////////////
// Utility functions
//////////////////////////////////
template<typename TAlign>
static inline void* aligned_malloc(size_t size)
{
if (std::alignment_of<TAlign>::value <= std::alignment_of<details::max_align_t>::value)
return (Traits::malloc)(size);
size_t alignment = std::alignment_of<TAlign>::value;
void* raw = (Traits::malloc)(size + alignment - 1 + sizeof(void*));
if (!raw)
return nullptr;
char* ptr = details::align_for<TAlign>(reinterpret_cast<char*>(raw) + sizeof(void*));
*(reinterpret_cast<void**>(ptr) - 1) = raw;
return ptr;
}
template<typename TAlign>
static inline void aligned_free(void* ptr)
{
if (std::alignment_of<TAlign>::value <= std::alignment_of<details::max_align_t>::value)
return (Traits::free)(ptr);
(Traits::free)(ptr ? *(reinterpret_cast<void**>(ptr) - 1) : nullptr);
}
template<typename U>
static inline U* create_array(size_t count)
{
assert(count > 0);
auto p = static_cast<U*>((Traits::malloc)(sizeof(U) * count));
if (p == nullptr) {
U* p = static_cast<U*>(aligned_malloc<U>(sizeof(U) * count));
if (p == nullptr)
return nullptr;
}
for (size_t i = 0; i != count; ++i) {
for (size_t i = 0; i != count; ++i)
new (p + i) U();
}
return p;
}
template<typename U>
static inline void destroy_array(U* p, size_t count)
{
if (p != nullptr) {
assert(count > 0);
for (size_t i = count; i != 0; ) {
for (size_t i = count; i != 0; )
(p + --i)->~U();
}
(Traits::free)(p);
}
aligned_free<U>(p);
}
template<typename U>
static inline U* create()
{
auto p = (Traits::malloc)(sizeof(U));
void* p = aligned_malloc<U>(sizeof(U));
return p != nullptr ? new (p) U : nullptr;
}
template<typename U, typename A1>
static inline U* create(A1&& a1)
{
auto p = (Traits::malloc)(sizeof(U));
void* p = aligned_malloc<U>(sizeof(U));
return p != nullptr ? new (p) U(std::forward<A1>(a1)) : nullptr;
}
template<typename U>
static inline void destroy(U* p)
{
if (p != nullptr) {
if (p != nullptr)
p->~U();
}
(Traits::free)(p);
aligned_free<U>(p);
}
private:
@@ -3552,7 +3598,7 @@ private:
Block* initialBlockPool;
size_t initialBlockPoolSize;
#if !MCDBGQ_USEDEBUGFREELIST
#ifndef MCDBGQ_USEDEBUGFREELIST
FreeList<Block> freeList;
#else
debug::DebugFreeList<Block> freeList;
@@ -3567,7 +3613,7 @@ private:
std::atomic<std::uint32_t> nextExplicitConsumerId;
std::atomic<std::uint32_t> globalExplicitConsumerOffset;
#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
debug::DebugMutex implicitProdMutex;
#endif
@@ -3639,7 +3685,3 @@ inline void swap(typename ConcurrentQueue<T, Traits>::ImplicitProducerKVP& a, ty
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#if defined(_MSC_VAR)
#pragma warning(pop)
#endif