diff --git a/include/libconcur/circular_queue.h b/include/libconcur/circular_queue.h index d77a556..59c07e8 100644 --- a/include/libconcur/circular_queue.h +++ b/include/libconcur/circular_queue.h @@ -1,7 +1,7 @@ /** * \file libconcur/queue.h * \author mutouyun (orz@orzz.org) - * \brief Define concurrent queue. + * \brief Define concurrent circular queue. * \date 2022-11-19 */ #pragma once diff --git a/include/libconcur/intrusive_stack.h b/include/libconcur/intrusive_stack.h new file mode 100644 index 0000000..abaf735 --- /dev/null +++ b/include/libconcur/intrusive_stack.h @@ -0,0 +1,59 @@ +/** + * \file libconcur/intrusive_stack.h + * \author mutouyun (orz@orzz.org) + * \brief Define concurrent intrusive stack. + * \date 2023-11-18 + */ +#pragma once + +#include + +#include "libconcur/def.h" + +LIBCONCUR_NAMESPACE_BEG_ + +template +class intrusive_stack { +public: + struct node { + T value; + std::atomic next{nullptr}; + }; + +private: + std::atomic top_{nullptr}; + +public: + intrusive_stack(intrusive_stack const &) = delete; + intrusive_stack(intrusive_stack &&) = delete; + intrusive_stack &operator=(intrusive_stack const &) = delete; + intrusive_stack &operator=(intrusive_stack &&) = delete; + + constexpr intrusive_stack() noexcept = default; + + bool empty() const noexcept { + return top_.load(std::memory_order_acquire) == nullptr; + } + + void push(node *n) noexcept { + node *old_top = top_.load(std::memory_order_acquire); + do { + n->next.store(old_top, std::memory_order_relaxed); + } while (!top_.compare_exchange_weak(old_top, n, std::memory_order_release + , std::memory_order_acquire)); + } + + node *pop() noexcept { + node *old_top = top_.load(std::memory_order_acquire); + do { + if (old_top == nullptr) { + return nullptr; + } + } while (!top_.compare_exchange_weak(old_top, old_top->next.load(std::memory_order_relaxed) + , std::memory_order_release + , std::memory_order_acquire)); + return old_top; + } +}; + +LIBCONCUR_NAMESPACE_END_ diff --git a/test/concur/test_concur_stack.cpp b/test/concur/test_concur_stack.cpp new file mode 100644 index 0000000..6dc8428 --- /dev/null +++ b/test/concur/test_concur_stack.cpp @@ -0,0 +1,92 @@ + +#include "gtest/gtest.h" + +#define private public +#include "libconcur/intrusive_stack.h" + +TEST(intrusive_stack, construct) { + concur::intrusive_stack s; + EXPECT_TRUE(s.empty()); +} + +TEST(intrusive_stack, construct_node) { + concur::intrusive_stack::node n; + EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr); +} + +TEST(intrusive_stack, copyable) { + EXPECT_FALSE(std::is_copy_constructible>::value); + EXPECT_FALSE(std::is_copy_assignable>::value); +} + +TEST(intrusive_stack, moveable) { + EXPECT_FALSE(std::is_move_constructible>::value); + EXPECT_FALSE(std::is_move_assignable>::value); +} + +TEST(intrusive_stack, push_one) { + concur::intrusive_stack s; + concur::intrusive_stack::node n; + s.push(&n); + EXPECT_FALSE(s.empty()); + EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n); + EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr); +} + +TEST(intrusive_stack, push_many) { + concur::intrusive_stack s; + concur::intrusive_stack::node n1; + concur::intrusive_stack::node n2; + concur::intrusive_stack::node n3; + s.push(&n1); + s.push(&n2); + s.push(&n3); + EXPECT_FALSE(s.empty()); + EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n3); + EXPECT_TRUE(n3.next.load(std::memory_order_relaxed) == &n2); + EXPECT_TRUE(n2.next.load(std::memory_order_relaxed) == &n1); + EXPECT_TRUE(n1.next.load(std::memory_order_relaxed) == nullptr); +} + +TEST(intrusive_stack, push_same) { + concur::intrusive_stack s; + concur::intrusive_stack::node n; + s.push(&n); + s.push(&n); + EXPECT_FALSE(s.empty()); + EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n); + EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == &n); +} + +TEST(intrusive_stack, pop_empty) { + concur::intrusive_stack s; + EXPECT_TRUE(s.pop() == nullptr); +} + +TEST(intrusive_stack, pop_one) { + concur::intrusive_stack s; + concur::intrusive_stack::node n; + s.push(&n); + EXPECT_TRUE(s.pop() == &n); + EXPECT_TRUE(s.empty()); + EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == nullptr); + EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr); +} + +TEST(intrusive_stack, pop_many) { + concur::intrusive_stack s; + concur::intrusive_stack::node n1; + concur::intrusive_stack::node n2; + concur::intrusive_stack::node n3; + s.push(&n1); + s.push(&n2); + s.push(&n3); + EXPECT_TRUE(s.pop() == &n3); + EXPECT_TRUE(s.pop() == &n2); + EXPECT_TRUE(s.pop() == &n1); + EXPECT_TRUE(s.empty()); + EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == nullptr); + EXPECT_TRUE(n3.next.load(std::memory_order_relaxed) == &n2); + EXPECT_TRUE(n2.next.load(std::memory_order_relaxed) == &n1); + EXPECT_TRUE(n1.next.load(std::memory_order_relaxed) == nullptr); +}