├── CustomIndexType └── LockFreeMPMCQueue.h ├── LICENSE ├── MPMCQueue.h ├── README.md └── Tests ├── Main.cpp └── MutexQueue.h /CustomIndexType/LockFreeMPMCQueue.h: -------------------------------------------------------------------------------- 1 | // THIS FILE IS NOT MAINTAINED AND MAY NOT CONTAIN ALL FIXES IN ../LockFreeMPMPCQueue.h 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | template class LockFreeMPMCQueue 8 | { 9 | public: 10 | explicit LockFreeMPMCQueue( size_t size ) 11 | : m_data( new T[size] ), m_size( size ), m_head_1( 0 ), m_head_2( 0 ), m_tail_1( 0 ), m_tail_2( 0 ) 12 | { 13 | } 14 | 15 | virtual ~LockFreeMPMCQueue() { delete[] m_data; } 16 | 17 | bool try_enqueue( const T& value ) 18 | { 19 | index_t tail = m_tail_1.load( std::memory_order_relaxed ); 20 | const index_t head = m_head_2.load( std::memory_order_relaxed ); 21 | 22 | const index_t count = 23 | tail > head ? tail - head : ( std::numeric_limits::max() - head ) + tail + 1; 24 | 25 | // count could be greater than size if between the reading of head, and the reading of tail, both head 26 | // and tail have been advanced 27 | if( count >= m_size ) 28 | { 29 | return false; 30 | } 31 | 32 | const index_t next_tail = tail < std::numeric_limits::max() ? tail + 1 : 0; 33 | 34 | if( !std::atomic_compare_exchange_strong_explicit( 35 | &m_tail_1, &tail, next_tail, std::memory_order_relaxed, std::memory_order_relaxed ) ) 36 | { 37 | return false; 38 | } 39 | 40 | m_data[tail % m_size] = value; 41 | 42 | while( m_tail_2.load( std::memory_order_relaxed ) != tail ) 43 | { 44 | std::this_thread::yield(); 45 | } 46 | 47 | // Release - read/write before can't be reordered with writes after 48 | // Make sure the write of the value to m_data is 49 | // not reordered past the write to m_tail_2 50 | std::atomic_thread_fence( std::memory_order_release ); 51 | m_tail_2.store( tail + 1, std::memory_order_relaxed ); 52 | 53 | return true; 54 | } 55 | 56 | bool try_dequeue( T& out ) 57 | { 58 | // Order matters here, because we're testing for emptiness with head==tail 59 | // Could test with head >= tail, but if index_t is < 64bits then this will have 60 | // issues when tail has wrapped around to 0 61 | index_t head = m_head_1.load( std::memory_order_relaxed ); 62 | // Acquire - read before can't be reordered with read/write after 63 | std::atomic_thread_fence( std::memory_order_acquire ); 64 | const index_t tail = m_tail_2.load( std::memory_order_relaxed ); 65 | 66 | if( head == tail ) 67 | { 68 | return false; 69 | } 70 | 71 | const index_t next_head = head < std::numeric_limits::max() ? head + 1 : 0; 72 | 73 | if( !std::atomic_compare_exchange_strong_explicit( 74 | &m_head_1, &head, next_head, std::memory_order_relaxed, std::memory_order_relaxed ) ) 75 | { 76 | return false; 77 | } 78 | 79 | std::atomic_thread_fence( std::memory_order_acquire ); 80 | out = m_data[head % m_size]; 81 | 82 | while( m_head_2.load( std::memory_order_relaxed ) != head ) 83 | { 84 | std::this_thread::yield(); 85 | } 86 | 87 | // Release - read/write before can't be reordered with writes after 88 | // Make sure the read of value from m_data is 89 | // not reordered past the write to m_head_2 90 | std::atomic_thread_fence( std::memory_order_release ); 91 | m_head_2.store( head + 1, std::memory_order_relaxed ); 92 | 93 | return true; 94 | } 95 | 96 | size_t capacity() const { return m_size; } 97 | 98 | private: 99 | T* m_data; 100 | size_t m_size; 101 | char pad1[64]; 102 | std::atomic m_head_1; 103 | char pad2[64]; 104 | std::atomic m_head_2; 105 | char pad3[64]; 106 | std::atomic m_tail_1; 107 | char pad4[64]; 108 | std::atomic m_tail_2; 109 | }; 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Joe Best-Rotheray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MPMCQueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template class MPMCQueue 7 | { 8 | public: 9 | explicit MPMCQueue( size_t capacity ) 10 | : m_items( static_cast( _aligned_malloc( sizeof( Item ) * capacity, cache_line_size ) ) ) 11 | , m_capacity( capacity ) 12 | , m_head( 0 ) 13 | , m_tail( 0 ) 14 | { 15 | for( size_t i = 0; i < capacity; ++i ) 16 | { 17 | m_items[i].version = i; 18 | } 19 | } 20 | 21 | virtual ~MPMCQueue() { _aligned_free( m_items ); } 22 | 23 | // non-copyable 24 | MPMCQueue( const MPMCQueue& ) = delete; 25 | MPMCQueue( const MPMCQueue&& ) = delete; 26 | MPMCQueue& operator=( const MPMCQueue& ) = delete; 27 | MPMCQueue& operator=( const MPMCQueue&& ) = delete; 28 | 29 | bool try_enqueue( const T& value ) 30 | { 31 | uint64_t tail = m_tail.load( std::memory_order_relaxed ); 32 | 33 | if( m_items[tail % m_capacity].version.load( std::memory_order_acquire ) != tail ) 34 | { 35 | return false; 36 | } 37 | 38 | if( !m_tail.compare_exchange_strong( tail, tail + 1, std::memory_order_relaxed ) ) 39 | { 40 | return false; 41 | } 42 | 43 | m_items[tail % m_capacity].value = value; 44 | 45 | // Release operation, all reads/writes before this store cannot be reordered past it 46 | // Writing version to tail + 1 signals reader threads when to read payload 47 | m_items[tail % m_capacity].version.store( tail + 1, std::memory_order_release ); 48 | 49 | return true; 50 | } 51 | 52 | bool try_dequeue( T& out ) 53 | { 54 | uint64_t head = m_head.load( std::memory_order_relaxed ); 55 | 56 | // Acquire here makes sure read of m_data[head].value is not reordered before this 57 | // Also makes sure side effects in try_enqueue are visible here 58 | if( m_items[head % m_capacity].version.load( std::memory_order_acquire ) != (head + 1) ) 59 | { 60 | return false; 61 | } 62 | 63 | if( !m_head.compare_exchange_strong( head, head + 1, std::memory_order_relaxed ) ) 64 | { 65 | return false; 66 | } 67 | 68 | out = m_items[head % m_capacity].value; 69 | 70 | // This signals to writer threads that they can now write something to this index 71 | m_items[head % m_capacity].version.store( head + m_capacity, std::memory_order_release ); 72 | 73 | return true; 74 | } 75 | 76 | size_t capacity() const { return m_capacity; } 77 | 78 | private: 79 | struct alignas(cache_line_size)Item 80 | { 81 | std::atomic version; 82 | T value; 83 | }; 84 | 85 | struct alignas(cache_line_size)AlignedAtomicU64 : public std::atomic 86 | { 87 | using std::atomic::atomic; 88 | }; 89 | 90 | Item* m_items; 91 | size_t m_capacity; 92 | 93 | // Make sure each index is on a different cache line 94 | AlignedAtomicU64 m_head; 95 | AlignedAtomicU64 m_tail; 96 | }; 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A fast (not quite lock-free) multi-producer multi-consumer queue 2 | 3 | ## The only file needed is MPMCQueue.h 4 | 5 | **CustomIndexType/** - some old code no longer maintained, but shows a strategy for dealing with index overflow if 64-bit CAS operations are not lock-free on a given platform 6 | 7 | **Tests/** - some slightly unsightly code to compare this queue against a naive mutex implementation, and spits out a google chart to show results 8 | -------------------------------------------------------------------------------- /Tests/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../MPMCQueue.h" 7 | #include "MutexQueue.h" 8 | 9 | // this is a horrible mess :( 10 | 11 | template