diff --git a/include/libipc/concur/intrusive_stack.h b/include/libipc/concur/intrusive_stack.h new file mode 100644 index 0000000..aa98a66 --- /dev/null +++ b/include/libipc/concur/intrusive_stack.h @@ -0,0 +1,66 @@ +/** + * \file libconcur/intrusive_stack.h + * \author mutouyun (orz@orzz.org) + * \brief Define concurrent intrusive stack. + */ +#pragma once + +#include + +namespace ipc { +namespace concur { + +/// \brief Intrusive stack node. +/// \tparam T The type of the value. +template +struct intrusive_node { + T value; + std::atomic next; +}; + +/// \brief Intrusive stack. +/// \tparam T The type of the value. +/// \tparam Node The type of the node. +template > +class intrusive_stack { +public: + using node = Node; + +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; + } +}; + +} // namespace concur +} // namespace ipc diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8b127de..8138466 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,6 +20,7 @@ file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/test/test_*.cpp ${LIBIPC_PROJECT_DIR}/test/imp/*.cpp ${LIBIPC_PROJECT_DIR}/test/mem/*.cpp + ${LIBIPC_PROJECT_DIR}/test/concur/*.cpp # ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp ) file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/test_*.h) diff --git a/test/concur/test_concur_intrusive_stack.cpp b/test/concur/test_concur_intrusive_stack.cpp new file mode 100644 index 0000000..41ec6ac --- /dev/null +++ b/test/concur/test_concur_intrusive_stack.cpp @@ -0,0 +1,103 @@ + +#include "test.h" + +#define private public +#include "libipc/concur/intrusive_stack.h" + +using namespace ipc; + +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{123}; + 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); + EXPECT_EQ(n.value, 123); +} + +TEST(intrusive_stack, push_many) { + concur::intrusive_stack s; + concur::intrusive_stack::node n1{111111}; + concur::intrusive_stack::node n2{222222}; + concur::intrusive_stack::node n3{333333}; + 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); + EXPECT_EQ(n1.value, 111111); + EXPECT_EQ(n2.value, 222222); + EXPECT_EQ(n3.value, 333333); +} + +TEST(intrusive_stack, push_same) { + concur::intrusive_stack s; + concur::intrusive_stack::node n{321}; + 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); + EXPECT_EQ(n.value, 321); +} + +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{112233}; + 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); + EXPECT_EQ(n.value, 112233); +} + +TEST(intrusive_stack, pop_many) { + concur::intrusive_stack s; + concur::intrusive_stack::node n1{111111}; + concur::intrusive_stack::node n2{222222}; + concur::intrusive_stack::node n3{333333}; + 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); + EXPECT_EQ(n1.value, 111111); + EXPECT_EQ(n2.value, 222222); + EXPECT_EQ(n3.value, 333333); +}