mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-08-05 09:28:40 +02:00
Add ReaderWriterQueue and update ConcurrentQueue
This commit is contained in:
+139
@@ -0,0 +1,139 @@
|
||||
#include "../../../atomicops.h"
|
||||
#include <cstdlib> // For std::size_t
|
||||
|
||||
// From http://www.1024cores.net/home/lock-free-algorithms/queues/unbounded-spsc-queue
|
||||
// (and http://software.intel.com/en-us/articles/single-producer-single-consumer-queue)
|
||||
|
||||
// load with 'consume' (data-dependent) memory ordering
|
||||
template<typename T>
|
||||
T load_consume(T const* addr)
|
||||
{
|
||||
// hardware fence is implicit on x86
|
||||
T v = *const_cast<T const volatile*>(addr);
|
||||
moodycamel::compiler_fence(moodycamel::memory_order_seq_cst);
|
||||
return v;
|
||||
}
|
||||
|
||||
// store with 'release' memory ordering
|
||||
template<typename T>
|
||||
void store_release(T* addr, T v)
|
||||
{
|
||||
// hardware fence is implicit on x86
|
||||
moodycamel::compiler_fence(moodycamel::memory_order_seq_cst);
|
||||
*const_cast<T volatile*>(addr) = v;
|
||||
}
|
||||
|
||||
// cache line size on modern x86 processors (in bytes)
|
||||
size_t const cache_line_size = 64;
|
||||
// single-producer/single-consumer queue
|
||||
template<typename T>
|
||||
class spsc_queue
|
||||
{
|
||||
public:
|
||||
spsc_queue()
|
||||
{
|
||||
node* n = new node;
|
||||
n->next_ = 0;
|
||||
tail_ = head_ = first_= tail_copy_ = n;
|
||||
}
|
||||
|
||||
explicit spsc_queue(size_t prealloc)
|
||||
{
|
||||
node* n = new node;
|
||||
n->next_ = 0;
|
||||
tail_ = head_ = first_ = tail_copy_ = n;
|
||||
|
||||
// [CD] Not (at all) the most efficient way to pre-allocate memory, but it works
|
||||
T dummy = T();
|
||||
for (size_t i = 0; i != prealloc; ++i) {
|
||||
enqueue(dummy);
|
||||
}
|
||||
for (size_t i = 0; i != prealloc; ++i) {
|
||||
try_dequeue(dummy);
|
||||
}
|
||||
}
|
||||
|
||||
~spsc_queue()
|
||||
{
|
||||
node* n = first_;
|
||||
do
|
||||
{
|
||||
node* next = n->next_;
|
||||
delete n;
|
||||
n = next;
|
||||
}
|
||||
while (n);
|
||||
}
|
||||
|
||||
void enqueue(T v)
|
||||
{
|
||||
node* n = alloc_node();
|
||||
n->next_ = 0;
|
||||
n->value_ = v;
|
||||
store_release(&head_->next_, n);
|
||||
head_ = n;
|
||||
}
|
||||
|
||||
// returns 'false' if queue is empty
|
||||
bool try_dequeue(T& v)
|
||||
{
|
||||
if (load_consume(&tail_->next_))
|
||||
{
|
||||
v = tail_->next_->value_;
|
||||
store_release(&tail_, tail_->next_);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// internal node structure
|
||||
struct node
|
||||
{
|
||||
node* next_;
|
||||
T value_;
|
||||
};
|
||||
|
||||
// consumer part
|
||||
// accessed mainly by consumer, infrequently be producer
|
||||
node* tail_; // tail of the queue
|
||||
|
||||
// delimiter between consumer part and producer part,
|
||||
// so that they situated on different cache lines
|
||||
char cache_line_pad_ [cache_line_size];
|
||||
|
||||
// producer part
|
||||
// accessed only by producer
|
||||
node* head_; // head of the queue
|
||||
node* first_; // last unused node (tail of node cache)
|
||||
node* tail_copy_; // helper (points somewhere between first_ and tail_)
|
||||
|
||||
node* alloc_node()
|
||||
{
|
||||
// first tries to allocate node from internal node cache,
|
||||
// if attempt fails, allocates node via ::operator new()
|
||||
|
||||
if (first_ != tail_copy_)
|
||||
{
|
||||
node* n = first_;
|
||||
first_ = first_->next_;
|
||||
return n;
|
||||
}
|
||||
tail_copy_ = load_consume(&tail_);
|
||||
if (first_ != tail_copy_)
|
||||
{
|
||||
node* n = first_;
|
||||
first_ = first_->next_;
|
||||
return n;
|
||||
}
|
||||
node* n = new node;
|
||||
return n;
|
||||
}
|
||||
|
||||
spsc_queue(spsc_queue const&);
|
||||
spsc_queue& operator = (spsc_queue const&);
|
||||
|
||||
};
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
// Adapted from https://github.com/facebook/folly/blob/master/folly/ProducerConsumerQueue.h
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
// @author Bo Hu (bhu@fb.com)
|
||||
// @author Jordan DeLong (delong.j@fb.com)
|
||||
|
||||
#ifndef PRODUCER_CONSUMER_QUEUE_H_
|
||||
#define PRODUCER_CONSUMER_QUEUE_H_
|
||||
|
||||
#include <new>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
//#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/*
|
||||
* ProducerConsumerQueue is a one producer and one consumer queue
|
||||
* without locks.
|
||||
*/
|
||||
template<class T>
|
||||
struct ProducerConsumerQueue {
|
||||
typedef T value_type;
|
||||
|
||||
// size must be >= 1.
|
||||
explicit ProducerConsumerQueue(uint32_t size)
|
||||
: size_(size + 1) // +1 because one slot is always empty
|
||||
, records_(static_cast<T*>(std::malloc(sizeof(T) * (size + 1))))
|
||||
, readIndex_(0)
|
||||
, writeIndex_(0)
|
||||
{
|
||||
assert(size >= 1);
|
||||
if (!records_) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}
|
||||
|
||||
~ProducerConsumerQueue() {
|
||||
// We need to destruct anything that may still exist in our queue.
|
||||
// (No real synchronization needed at destructor time: only one
|
||||
// thread can be doing this.)
|
||||
if (!std::is_trivially_destructible<T>::value) {
|
||||
int read = readIndex_;
|
||||
int end = writeIndex_;
|
||||
while (read != end) {
|
||||
records_[read].~T();
|
||||
if (++read == size_) {
|
||||
read = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::free(records_);
|
||||
}
|
||||
|
||||
template<class ...Args>
|
||||
bool enqueue(Args&&... recordArgs) {
|
||||
auto const currentWrite = writeIndex_.load(std::memory_order_relaxed);
|
||||
auto nextRecord = currentWrite + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
if (nextRecord != readIndex_.load(std::memory_order_acquire)) {
|
||||
new (&records_[currentWrite]) T(std::forward<Args>(recordArgs)...);
|
||||
writeIndex_.store(nextRecord, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
// queue is full
|
||||
return false;
|
||||
}
|
||||
|
||||
// move (or copy) the value at the front of the queue to given variable
|
||||
bool try_dequeue(T& record) {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
|
||||
// queue is empty
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nextRecord = currentRead + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
record = std::move(records_[currentRead]);
|
||||
records_[currentRead].~T();
|
||||
readIndex_.store(nextRecord, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
// pointer to the value at the front of the queue (for use in-place) or
|
||||
// nullptr if empty.
|
||||
T* frontPtr() {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
|
||||
// queue is empty
|
||||
return nullptr;
|
||||
}
|
||||
return &records_[currentRead];
|
||||
}
|
||||
|
||||
// queue must not be empty
|
||||
void popFront() {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
assert(currentRead != writeIndex_.load(std::memory_order_acquire));
|
||||
|
||||
auto nextRecord = currentRead + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
records_[currentRead].~T();
|
||||
readIndex_.store(nextRecord, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return readIndex_.load(std::memory_order_consume) ==
|
||||
writeIndex_.load(std::memory_order_consume);
|
||||
}
|
||||
|
||||
bool isFull() const {
|
||||
auto nextRecord = writeIndex_.load(std::memory_order_consume) + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
if (nextRecord != readIndex_.load(std::memory_order_consume)) {
|
||||
return false;
|
||||
}
|
||||
// queue is full
|
||||
return true;
|
||||
}
|
||||
|
||||
// * If called by consumer, then true size may be more (because producer may
|
||||
// be adding items concurrently).
|
||||
// * If called by producer, then true size may be less (because consumer may
|
||||
// be removing items concurrently).
|
||||
// * It is undefined to call this from any other thread.
|
||||
size_t sizeGuess() const {
|
||||
int ret = writeIndex_.load(std::memory_order_consume) -
|
||||
readIndex_.load(std::memory_order_consume);
|
||||
if (ret < 0) {
|
||||
ret += size_;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t size_;
|
||||
T* const records_;
|
||||
|
||||
std::atomic<int> readIndex_;
|
||||
std::atomic<int> writeIndex_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user