// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // A Multiple Producer, Single Consumer Queue. // It allows multiple threads to enqueue, and allows one thread // (and only one thread) to dequeue. #ifndef BUTIL_MPSC_QUEUE_H #define BUTIL_MPSC_QUEUE_H #include "butil/object_pool.h" #include "butil/type_traits.h" #include "butil/memory/manual_constructor.h" namespace butil { template struct BAIDU_CACHELINE_ALIGNMENT MPSCQueueNode { static MPSCQueueNode* const UNCONNECTED; MPSCQueueNode* next{NULL}; ManualConstructor data_mem; }; template MPSCQueueNode* const MPSCQueueNode::UNCONNECTED = (MPSCQueueNode*)(intptr_t)-1; // Default allocator for MPSCQueueNode. template class DefaultAllocator { public: void* Alloc() { return malloc(sizeof(MPSCQueueNode)); } void Free(void* p) { free(p); } }; // Allocator using ObjectPool for MPSCQueueNode. template class ObjectPoolAllocator { public: void* Alloc() { return get_object>(); } void Free(void* p) { return_object(static_cast*>(p)); } }; template > class MPSCQueue { public: MPSCQueue() : _head(NULL) , _cur_enqueue_node(NULL) , _cur_dequeue_node(NULL) {} ~MPSCQueue(); // Enqueue data to the queue. void Enqueue(typename add_const_reference::type data); void Enqueue(T&& data); // Dequeue data from the queue. bool Dequeue(T& data); private: // Reverse the list until old_head. void ReverseList(MPSCQueueNode* old_head); void EnqueueImpl(MPSCQueueNode* node); bool DequeueImpl(T* data); Alloc _alloc; atomic*> _head; atomic*> _cur_enqueue_node; MPSCQueueNode* _cur_dequeue_node; }; template MPSCQueue::~MPSCQueue() { while (DequeueImpl(NULL)); } template void MPSCQueue::Enqueue(typename add_const_reference::type data) { auto node = (MPSCQueueNode*)_alloc.Alloc(); node->next = MPSCQueueNode::UNCONNECTED; node->data_mem.Init(data); EnqueueImpl(node); } template void MPSCQueue::Enqueue(T&& data) { auto node = (MPSCQueueNode*)_alloc.Alloc(); node->next = MPSCQueueNode::UNCONNECTED; node->data_mem.Init(data); EnqueueImpl(node); } template void MPSCQueue::EnqueueImpl(MPSCQueueNode* node) { MPSCQueueNode* prev = _head.exchange(node, memory_order_release); if (prev) { node->next = prev; return; } node->next = NULL; _cur_enqueue_node.store(node, memory_order_relaxed); } template bool MPSCQueue::Dequeue(T& data) { return DequeueImpl(&data); } template bool MPSCQueue::DequeueImpl(T* data) { MPSCQueueNode* node; if (_cur_dequeue_node) { node = _cur_dequeue_node; } else { node = _cur_enqueue_node.exchange(NULL, memory_order_relaxed); } if (!node) { return false; } if (data) { auto mem = (T* const)node->data_mem.get(); *data = std::move(*mem); } MPSCQueueNode* old_node = node; if (!node->next) { ReverseList(node); } _cur_dequeue_node = node->next; _alloc.Free(old_node); return true; } template void MPSCQueue::ReverseList(MPSCQueueNode* old_head) { // Try to set _write_head to NULL to mark that it is done. MPSCQueueNode* new_head = old_head; MPSCQueueNode* desired = NULL; if (_head.compare_exchange_strong( new_head, desired, memory_order_acquire)) { // No one added new requests. return; } CHECK_NE(new_head, old_head); // Above acquire fence pairs release fence of exchange in Enqueue() to make // sure that we see all fields of requests set. // Someone added new requests. // Reverse the list until old_head. MPSCQueueNode* tail = NULL; MPSCQueueNode* p = new_head; do { while (p->next == MPSCQueueNode::UNCONNECTED) { // TODO(gejun): elaborate this sched_yield(); } MPSCQueueNode* const saved_next = p->next; p->next = tail; tail = p; p = saved_next; CHECK(p); } while (p != old_head); // Link old list with new list. old_head->next = tail; } } #endif // BUTIL_MPSC_QUEUE_H