ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
work_stealing_deque.hpp
Go to the documentation of this file.
1#pragma once
2
7#include <atomic>
8#include <cstddef>
9#include <memory>
10#include <mutex>
11#include <type_traits>
12#include <utility>
13
15{
16
17template <typename T>
19{
20public:
21 static constexpr size_t cache_line_size = 64;
22 static constexpr size_t default_capacity = 1024;
23
24private:
25 struct alignas(cache_line_size) aligned_item
26 {
27 T item;
28 aligned_item() = default;
29 aligned_item(T&& value) : item(std::move(value)) {}
30 template <typename U = T, std::enable_if_t<std::is_copy_constructible_v<U>, int> = 0>
31 aligned_item(T const& value) : item(value)
32 {
33 }
34 };
35
36 std::unique_ptr<aligned_item[]> buffer_;
37 size_t capacity_;
38 alignas(cache_line_size) std::atomic<size_t> top_{ 0 };
39 alignas(cache_line_size) std::atomic<size_t> bottom_{ 0 };
40 alignas(cache_line_size) mutable std::mutex mutex_;
41
42public:
43 explicit work_stealing_deque(size_t capacity = default_capacity)
44 : buffer_(std::make_unique<aligned_item[]>(capacity)), capacity_(capacity)
45 {
46 }
47
48 [[nodiscard]] auto
49 push(T&& item) -> bool
50 {
51 std::lock_guard<std::mutex> lock(mutex_);
52 size_t const top = top_.load(std::memory_order_relaxed);
53 size_t const bottom = bottom_.load(std::memory_order_relaxed);
54 if (top - bottom >= capacity_)
55 return false;
56 buffer_[top % capacity_] = aligned_item(std::move(item));
57 top_.store(top + 1, std::memory_order_release);
58 return true;
59 }
60
61 template <typename U = T, std::enable_if_t<std::is_copy_constructible_v<U>, int> = 0>
62 [[nodiscard]] auto
63 push(T const& item) -> bool
64 {
65 std::lock_guard<std::mutex> lock(mutex_);
66 size_t const top = top_.load(std::memory_order_relaxed);
67 size_t const bottom = bottom_.load(std::memory_order_relaxed);
68 if (top - bottom >= capacity_)
69 return false;
70 buffer_[top % capacity_] = aligned_item(item);
71 top_.store(top + 1, std::memory_order_release);
72 return true;
73 }
74
75 [[nodiscard]] auto
76 pop(T& item) -> bool
77 {
78 std::lock_guard<std::mutex> lock(mutex_);
79 size_t const top = top_.load(std::memory_order_relaxed);
80 size_t const bottom = bottom_.load(std::memory_order_relaxed);
81 if (top <= bottom)
82 return false;
83 size_t const new_top = top - 1;
84 item = std::move(buffer_[new_top % capacity_].item);
85 top_.store(new_top, std::memory_order_relaxed);
86 return true;
87 }
88
89 [[nodiscard]] auto
90 steal(T& item) -> bool
91 {
92 std::lock_guard<std::mutex> lock(mutex_);
93 size_t const bottom = bottom_.load(std::memory_order_relaxed);
94 size_t const top = top_.load(std::memory_order_relaxed);
95 if (bottom >= top)
96 return false;
97 item = std::move(buffer_[bottom % capacity_].item);
98 bottom_.store(bottom + 1, std::memory_order_relaxed);
99 return true;
100 }
101
102 [[nodiscard]] auto
103 size() const -> size_t
104 {
105 size_t const top = top_.load(std::memory_order_relaxed);
106 size_t const bottom = bottom_.load(std::memory_order_relaxed);
107 return top > bottom ? top - bottom : 0;
108 }
109
110 [[nodiscard]] auto
111 empty() const -> bool
112 {
113 return size() == 0;
114 }
115
116 void
118 {
119 (void)clear_and_count();
120 }
121
122 [[nodiscard]] auto
123 clear_and_count() -> size_t
124 {
125 size_t count = 0;
126 T discarded;
127 while (steal(discarded))
128 {
129 ++count;
130 discarded = T{};
131 }
132 return count;
133 }
134};
135
136} // namespace threadschedule::detail
work_stealing_deque(size_t capacity=default_capacity)
Aggregates multiple thread_registry_backend instances into a single queryable view.