mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-07 01:06:45 +08:00
add: [concur] queue and simple ut
This commit is contained in:
parent
934bb31778
commit
426b7c3768
@ -6,10 +6,14 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "libimp/construct.h"
|
#include "libimp/construct.h"
|
||||||
#include "libimp/detect_plat.h"
|
#include "libimp/detect_plat.h"
|
||||||
|
#include "libimp/aligned.h"
|
||||||
|
|
||||||
#include "libpmr/allocator.h"
|
#include "libpmr/allocator.h"
|
||||||
#include "libpmr/memory_resource.h"
|
#include "libpmr/memory_resource.h"
|
||||||
@ -19,27 +23,55 @@
|
|||||||
|
|
||||||
LIBCONCUR_NAMESPACE_BEG_
|
LIBCONCUR_NAMESPACE_BEG_
|
||||||
|
|
||||||
template <typename T, typename PRelationT, typename CRelationT>
|
template <typename T, typename PRelationT = relation::multi
|
||||||
|
, typename CRelationT = relation::multi>
|
||||||
class queue {
|
class queue {
|
||||||
public:
|
public:
|
||||||
using producer_relation_t = PRelationT;
|
using producer_relation_t = PRelationT;
|
||||||
using consumer_relation_t = CRelationT;
|
using consumer_relation_t = CRelationT;
|
||||||
using model_type = prod_cons<trans::unicast, producer_relation_t, consumer_relation_t>;
|
using model_type = prod_cons<trans::unicast, producer_relation_t, consumer_relation_t>;
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
|
using size_type = std::int64_t;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct data {
|
struct data {
|
||||||
model_type model_;
|
model_type model_;
|
||||||
typename concur::traits<model_type>::header header_;
|
typename concur::traits<model_type>::header header_;
|
||||||
element<value_type> elements_start_;
|
::LIBIMP::aligned<element<value_type>> elements_start_;
|
||||||
|
|
||||||
template <typename U>
|
template <typename U>
|
||||||
data(U &&model) noexcept
|
data(U &&model) noexcept
|
||||||
: header_(std::forward<U>(model)) {}
|
: header_(std::forward<U>(model)) {
|
||||||
|
auto elements = this->elements();
|
||||||
|
decltype(elements)::size_type i = 0;
|
||||||
|
LIBIMP_TRY {
|
||||||
|
for (; i < elements.size(); ++i) {
|
||||||
|
(void)::LIBIMP::construct<element<value_type>>(&elements[i]);
|
||||||
|
}
|
||||||
|
} LIBIMP_CATCH(...) {
|
||||||
|
for (decltype(i) k = 0; k < i; ++k) {
|
||||||
|
(void)::LIBIMP::destroy<element<value_type>>(&elements[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~data() noexcept {
|
||||||
|
for (auto &elem : this->elements()) {
|
||||||
|
(void)::LIBIMP::destroy<element<value_type>>(&elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::size_t size_of(index_t circ_size) noexcept {
|
||||||
|
return sizeof(struct data) + ( (circ_size - 1) * sizeof(element<value_type>) );
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t byte_size() const noexcept {
|
||||||
|
return size_of(header_.circ_size);
|
||||||
|
}
|
||||||
|
|
||||||
/// \brief element<value_type> elements[0];
|
/// \brief element<value_type> elements[0];
|
||||||
::LIBIMP::span<element<value_type>> elements() noexcept {
|
::LIBIMP::span<element<value_type>> elements() noexcept {
|
||||||
return {&elements_start_, header_.circ_size};
|
return {elements_start_.ptr(), header_.circ_size};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,7 +80,7 @@ private:
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
LIBIMP_TRY {
|
LIBIMP_TRY {
|
||||||
auto data_ptr = data_allocator_.allocate(sizeof(data) + (circ_size - 1) * sizeof(element<value_type>));
|
auto data_ptr = data_allocator_.alloc(data::size_of(circ_size));
|
||||||
if (data_ptr == nullptr) {
|
if (data_ptr == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -59,43 +91,72 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
::LIBPMR::allocator data_allocator_;
|
::LIBPMR::allocator data_allocator_;
|
||||||
|
std::atomic<size_type> size_;
|
||||||
data *data_;
|
data *data_;
|
||||||
typename concur::traits<model_type>::context context_;
|
typename concur::traits<model_type>::context context_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
queue() = default;
|
|
||||||
queue(queue const &) = delete;
|
queue(queue const &) = delete;
|
||||||
queue(queue &&) = delete;
|
queue(queue &&) = delete;
|
||||||
queue &operator=(queue const &) = delete;
|
queue &operator=(queue const &) = delete;
|
||||||
queue &operator=(queue &&) = delete;
|
queue &operator=(queue &&) = delete;
|
||||||
|
|
||||||
template <typename MR, ::LIBPMR::verify_memory_resource<T> = true>
|
~queue() noexcept {
|
||||||
explicit queue(index_t circ_size = default_circle_buffer_size,
|
if (valid()) {
|
||||||
MR *memory_resource = ::LIBPMR::new_delete_resource::get()) noexcept
|
auto sz = data_->byte_size();
|
||||||
|
(void)::LIBIMP::destroy<data>(data_);
|
||||||
|
data_allocator_.dealloc(data_, sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename MR, ::LIBPMR::verify_memory_resource<MR> = true>
|
||||||
|
explicit queue(index_t circ_size, MR *memory_resource) noexcept
|
||||||
: data_allocator_(memory_resource)
|
: data_allocator_(memory_resource)
|
||||||
, data_(init(circ_size)) {}
|
, data_(init(circ_size)) {}
|
||||||
|
|
||||||
template <typename MR, ::LIBPMR::verify_memory_resource<T> = true>
|
template <typename MR, ::LIBPMR::verify_memory_resource<MR> = true>
|
||||||
explicit queue(MR *memory_resource) noexcept
|
explicit queue(MR *memory_resource) noexcept
|
||||||
: queue(default_circle_buffer_size, memory_resource) {}
|
: queue(default_circle_buffer_size, memory_resource) {}
|
||||||
|
|
||||||
|
explicit queue(index_t circ_size) noexcept
|
||||||
|
: queue(circ_size, ::LIBPMR::new_delete_resource::get()) {}
|
||||||
|
|
||||||
|
queue() noexcept
|
||||||
|
: queue(default_circle_buffer_size) {}
|
||||||
|
|
||||||
bool valid() const noexcept {
|
bool valid() const noexcept {
|
||||||
return data_ != nullptr;
|
return (data_ != nullptr) && data_allocator_.valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit operator bool() const noexcept {
|
explicit operator bool() const noexcept {
|
||||||
return valid();
|
return valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_type approx_size() const noexcept {
|
||||||
|
return size_.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const noexcept {
|
||||||
|
return !valid() || (approx_size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename U>
|
template <typename U>
|
||||||
bool push(U &&value) noexcept {
|
bool push(U &&value) noexcept {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
return data_->model_.enqueue(data_->elements(), data_->header_, context_, std::forward<U>(value));
|
if (!data_->model_.enqueue(data_->elements(), data_->header_, context_, std::forward<U>(value))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pop(value_type &value) noexcept {
|
bool pop(value_type &value) noexcept {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
return data_->model_.dequeue(data_->elements(), data_->header_, context_, value);
|
if (!data_->model_.dequeue(data_->elements(), data_->header_, context_, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_.fetch_sub(1, std::memory_order_relaxed);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
62
include/libimp/aligned.h
Normal file
62
include/libimp/aligned.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* \file libimp/aligned.h
|
||||||
|
* \author mutouyun (orz@orzz.org)
|
||||||
|
* \brief Defines the type suitable for use as uninitialized storage for types of given type.
|
||||||
|
* \date 2023-05-20
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "libimp/def.h"
|
||||||
|
#include "libimp/byte.h"
|
||||||
|
|
||||||
|
LIBIMP_NAMESPACE_BEG_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief The type suitable for use as uninitialized storage for types of given type.
|
||||||
|
* std::aligned_storage is deprecated in C++23, so we define our own.
|
||||||
|
* \tparam T The type to be aligned.
|
||||||
|
* \tparam AlignT The alignment of the type.
|
||||||
|
* \see https://en.cppreference.com/w/cpp/types/aligned_storage
|
||||||
|
* https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf
|
||||||
|
*/
|
||||||
|
template <typename T, std::size_t AlignT = alignof(T)>
|
||||||
|
class aligned {
|
||||||
|
alignas(AlignT) std::array<::LIBIMP::byte, sizeof(T)> storage_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* \brief Returns a pointer to the aligned storage.
|
||||||
|
* \return A pointer to the aligned storage.
|
||||||
|
*/
|
||||||
|
T *ptr() noexcept {
|
||||||
|
return reinterpret_cast<T *>(storage_.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Returns a pointer to the aligned storage.
|
||||||
|
* \return A pointer to the aligned storage.
|
||||||
|
*/
|
||||||
|
T const *ptr() const noexcept {
|
||||||
|
return reinterpret_cast<const T *>(storage_.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Returns a reference to the aligned storage.
|
||||||
|
* \return A reference to the aligned storage.
|
||||||
|
*/
|
||||||
|
T &ref() noexcept {
|
||||||
|
return *ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Returns a reference to the aligned storage.
|
||||||
|
* \return A reference to the aligned storage.
|
||||||
|
*/
|
||||||
|
T const &ref() const noexcept {
|
||||||
|
return *ptr();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LIBIMP_NAMESPACE_END_
|
||||||
@ -36,14 +36,14 @@ class LIBIMP_EXPORT allocator {
|
|||||||
public:
|
public:
|
||||||
virtual ~holder_base() noexcept = default;
|
virtual ~holder_base() noexcept = default;
|
||||||
virtual void *alloc(std::size_t) = 0;
|
virtual void *alloc(std::size_t) = 0;
|
||||||
virtual void free (void *, std::size_t) = 0;
|
virtual void dealloc(void *, std::size_t) = 0;
|
||||||
virtual bool valid() const noexcept = 0;
|
virtual bool valid() const noexcept = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class holder_null : public holder_base {
|
class holder_null : public holder_base {
|
||||||
public:
|
public:
|
||||||
void *alloc(std::size_t) override { return nullptr; }
|
void *alloc(std::size_t) override { return nullptr; }
|
||||||
void free (void *, std::size_t) override {}
|
void dealloc(void *, std::size_t) override {}
|
||||||
bool valid() const noexcept override { return false; }
|
bool valid() const noexcept override { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ class LIBIMP_EXPORT allocator {
|
|||||||
return p_mem_res_->allocate(s);
|
return p_mem_res_->allocate(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void free(void *p, std::size_t s) override {
|
void dealloc(void *p, std::size_t s) override {
|
||||||
p_mem_res_->deallocate(p, s);
|
p_mem_res_->deallocate(p, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ public:
|
|||||||
|
|
||||||
/// \brief Allocate/deallocate memory.
|
/// \brief Allocate/deallocate memory.
|
||||||
void *alloc(std::size_t s);
|
void *alloc(std::size_t s);
|
||||||
void free (void *p, std::size_t s);
|
void dealloc(void *p, std::size_t s);
|
||||||
};
|
};
|
||||||
|
|
||||||
LIBPMR_NAMESPACE_END_
|
LIBPMR_NAMESPACE_END_
|
||||||
|
|||||||
@ -50,9 +50,9 @@ void *allocator::alloc(std::size_t s) {
|
|||||||
return get_holder().alloc(s);
|
return get_holder().alloc(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void allocator::free(void *p, std::size_t s) {
|
void allocator::dealloc(void *p, std::size_t s) {
|
||||||
if (!valid()) return;
|
if (!valid()) return;
|
||||||
get_holder().free(p, s);
|
get_holder().dealloc(p, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBPMR_NAMESPACE_END_
|
LIBPMR_NAMESPACE_END_
|
||||||
|
|||||||
@ -2,3 +2,61 @@
|
|||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include "libconcur/queue.h"
|
#include "libconcur/queue.h"
|
||||||
|
|
||||||
|
using namespace concur;
|
||||||
|
|
||||||
|
TEST(queue, construct) {
|
||||||
|
using queue_t = queue<int>;
|
||||||
|
queue_t q1;
|
||||||
|
EXPECT_TRUE(q1.valid());
|
||||||
|
EXPECT_TRUE(q1.empty());
|
||||||
|
EXPECT_EQ(q1.approx_size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(queue, push_pop) {
|
||||||
|
using queue_t = queue<int>;
|
||||||
|
queue_t q1;
|
||||||
|
EXPECT_TRUE(q1.valid());
|
||||||
|
EXPECT_TRUE(q1.empty());
|
||||||
|
EXPECT_EQ(q1.approx_size(), 0);
|
||||||
|
|
||||||
|
EXPECT_TRUE(q1.push(1));
|
||||||
|
EXPECT_FALSE(q1.empty());
|
||||||
|
EXPECT_EQ(q1.approx_size(), 1);
|
||||||
|
|
||||||
|
int value;
|
||||||
|
EXPECT_TRUE(q1.pop(value));
|
||||||
|
EXPECT_EQ(value, 1);
|
||||||
|
EXPECT_TRUE(q1.empty());
|
||||||
|
EXPECT_EQ(q1.approx_size(), 0);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
auto push = [&q1, &count](int i) {
|
||||||
|
EXPECT_TRUE(q1.push(i));
|
||||||
|
EXPECT_FALSE(q1.empty());
|
||||||
|
++count;
|
||||||
|
EXPECT_EQ(q1.approx_size(), count);
|
||||||
|
};
|
||||||
|
auto pop = [&q1, &count](int i) {
|
||||||
|
int value;
|
||||||
|
EXPECT_TRUE(q1.pop(value));
|
||||||
|
EXPECT_EQ(value, i);
|
||||||
|
--count;
|
||||||
|
EXPECT_EQ(q1.approx_size(), count);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
push(i);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
pop(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < default_circle_buffer_size; ++i) {
|
||||||
|
push(i);
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(q1.push(65536));
|
||||||
|
for (int i = 0; i < default_circle_buffer_size; ++i) {
|
||||||
|
pop(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,7 +20,7 @@ TEST(allocator, construct_with_memory_resource) {
|
|||||||
|
|
||||||
auto p = alc.alloc(128);
|
auto p = alc.alloc(128);
|
||||||
EXPECT_NE(p, nullptr);
|
EXPECT_NE(p, nullptr);
|
||||||
EXPECT_NO_THROW(alc.free(p, 128));
|
EXPECT_NO_THROW(alc.dealloc(p, 128));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(allocator, construct_copy_move) {
|
TEST(allocator, construct_copy_move) {
|
||||||
@ -64,15 +64,15 @@ TEST(allocator, invalid_alloc_free) {
|
|||||||
|
|
||||||
pmr::allocator alc1 {&mem_res}, alc2;
|
pmr::allocator alc1 {&mem_res}, alc2;
|
||||||
EXPECT_EQ(alc1.alloc(0), nullptr);
|
EXPECT_EQ(alc1.alloc(0), nullptr);
|
||||||
EXPECT_NO_THROW(alc1.free(nullptr, 128));
|
EXPECT_NO_THROW(alc1.dealloc(nullptr, 128));
|
||||||
EXPECT_NO_THROW(alc1.free(nullptr, 0));
|
EXPECT_NO_THROW(alc1.dealloc(nullptr, 0));
|
||||||
EXPECT_NO_THROW(alc1.free(&alc1, 0));
|
EXPECT_NO_THROW(alc1.dealloc(&alc1, 0));
|
||||||
|
|
||||||
EXPECT_EQ(alc2.alloc(0), nullptr);
|
EXPECT_EQ(alc2.alloc(0), nullptr);
|
||||||
EXPECT_NO_THROW(alc2.free(nullptr, 128));
|
EXPECT_NO_THROW(alc2.dealloc(nullptr, 128));
|
||||||
EXPECT_NO_THROW(alc2.free(nullptr, 0));
|
EXPECT_NO_THROW(alc2.dealloc(nullptr, 0));
|
||||||
EXPECT_NO_THROW(alc2.free(&alc1, 0));
|
EXPECT_NO_THROW(alc2.dealloc(&alc1, 0));
|
||||||
|
|
||||||
EXPECT_EQ(alc2.alloc(1024), nullptr);
|
EXPECT_EQ(alc2.alloc(1024), nullptr);
|
||||||
EXPECT_NO_THROW(alc2.free(&alc1, sizeof(alc1)));
|
EXPECT_NO_THROW(alc2.dealloc(&alc1, sizeof(alc1)));
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user