xenium
michael_scott_queue.hpp
1 //
2 // Copyright (c) 2018-2020 Manuel Pöter.
3 // Licensed under the MIT License. See LICENSE file in the project root for full license information.
4 //
5 
6 #ifndef XENIUM_MICHAEL_SCOTT_QUEUE_HPP
7 #define XENIUM_MICHAEL_SCOTT_QUEUE_HPP
8 
9 #include <xenium/acquire_guard.hpp>
10 #include <xenium/backoff.hpp>
11 #include <xenium/marked_ptr.hpp>
12 #include <xenium/parameter.hpp>
13 #include <xenium/policy.hpp>
14 
15 #ifdef _MSC_VER
16  #pragma warning(push)
17  #pragma warning(disable : 4324) // structure was padded due to alignment specifier
18 #endif
19 
20 namespace xenium {
37 template <class T, class... Policies>
39 public:
40  using value_type = T;
41  using reclaimer = parameter::type_param_t<policy::reclaimer, parameter::nil, Policies...>;
42  using backoff = parameter::type_param_t<policy::backoff, no_backoff, Policies...>;
43 
44  template <class... NewPolicies>
45  using with = michael_scott_queue<T, NewPolicies..., Policies...>;
46 
47  static_assert(parameter::is_set<reclaimer>::value, "reclaimer policy must be specified");
48 
51 
60  void push(T value);
61 
71  [[nodiscard]] bool try_pop(T& result);
72 
73 private:
74  struct node;
75 
76  using concurrent_ptr = typename reclaimer::template concurrent_ptr<node, 0>;
77  using marked_ptr = typename concurrent_ptr::marked_ptr;
78  using guard_ptr = typename concurrent_ptr::guard_ptr;
79 
80  struct node : reclaimer::template enable_concurrent_ptr<node> {
81  node() : _value(){};
82  explicit node(T&& v) : _value(std::move(v)) {}
83 
84  T _value;
85  concurrent_ptr _next;
86  };
87 
88  alignas(64) concurrent_ptr _head;
89  alignas(64) concurrent_ptr _tail;
90 };
91 
92 template <class T, class... Policies>
94  auto n = new node();
95  _head.store(n, std::memory_order_relaxed);
96  _tail.store(n, std::memory_order_relaxed);
97 }
98 
99 template <class T, class... Policies>
100 michael_scott_queue<T, Policies...>::~michael_scott_queue() {
101  // (1) - this acquire-load synchronizes-with the release-CAS (11)
102  auto n = _head.load(std::memory_order_acquire);
103  while (n) {
104  // (2) - this acquire-load synchronizes-with the release-CAS (6)
105  auto next = n->_next.load(std::memory_order_acquire);
106  delete n.get();
107  n = next;
108  }
109 }
110 
111 template <class T, class... Policies>
113  node* n = new node(std::move(value));
114 
115  backoff backoff;
116 
117  guard_ptr t;
118  for (;;) {
119  // Get the old _tail pointer.
120  // (3) - this acquire-load synchronizes-with the release-CAS (5, 7, 10)
121  t.acquire(_tail, std::memory_order_acquire);
122 
123  // Help update the _tail pointer if needed.
124  // (4) - this acquire-load synchronizes-with the release-CAS (6)
125  auto next = t->_next.load(std::memory_order_acquire);
126  if (next.get() != nullptr) {
127  marked_ptr expected(t.get());
128  // (5) - this release-CAS synchronizes-with the acquire-load (3)
129  _tail.compare_exchange_weak(expected, next, std::memory_order_release, std::memory_order_relaxed);
130  continue;
131  }
132 
133  // Attempt to link in the new element.
134  marked_ptr null{};
135  // (6) - this release-CAS synchronizes-with the acquire-load (2, 4, 9).
136  if (t->_next.compare_exchange_weak(null, n, std::memory_order_release, std::memory_order_relaxed)) {
137  break;
138  }
139 
140  backoff();
141  }
142 
143  // Swing the tail to the new element.
144  marked_ptr expected = t.get();
145  // (7) - this release-CAS synchronizes-with the acquire-load (3)
146  _tail.compare_exchange_strong(expected, n, std::memory_order_release, std::memory_order_relaxed);
147 }
148 
149 template <class T, class... Policies>
151  backoff backoff;
152 
153  guard_ptr h;
154  for (;;) {
155  // Get the old _head and _tail elements.
156  // (8) - this acquire-load synchronizes-with the release-CAS (11)
157  h.acquire(_head, std::memory_order_acquire);
158 
159  // Get the _head element's successor.
160  // (9) - this acquire-load synchronizes-with the release-CAS (6).
161  auto next = acquire_guard(h->_next, std::memory_order_acquire);
162  if (_head.load(std::memory_order_relaxed).get() != h.get()) {
163  continue;
164  }
165 
166  // If the _head (dummy) element is the only one, return false to signal that
167  // the operation has failed (no element has been returned).
168  if (next.get() == nullptr) {
169  return false;
170  }
171 
172  marked_ptr t = _tail.load(std::memory_order_relaxed);
173 
174  // There are multiple elements. Help update _tail if needed.
175  if (h.get() == t.get()) {
176  // (10) - this release-CAS synchronizes-with the acquire-load (3)
177  _tail.compare_exchange_weak(t, next, std::memory_order_release, std::memory_order_relaxed);
178  continue;
179  }
180 
181  // Attempt to update the _head pointer so that it points to the new dummy node.
182  marked_ptr expected(h.get());
183  // (11) - this release-CAS synchronizes-with the acquire-load (1, 8)
184  if (_head.compare_exchange_weak(expected, next, std::memory_order_release, std::memory_order_relaxed)) {
185  // return the data of _head's successor; it is the new dummy node.
186  result = std::move(next->_value);
187  break;
188  }
189 
190  backoff();
191  }
192 
193  // The old dummy node has been unlinked, so reclaim it.
194  h.reclaim();
195 
196  return true;
197 }
198 } // namespace xenium
199 
200 #ifdef _MSC_VER
201  #pragma warning(pop)
202 #endif
203 
204 #endif
xenium::policy::backoff
Policy to configure the backoff strategy.
Definition: policy.hpp:39
xenium::michael_scott_queue::push
void push(T value)
Pushes the given value to the queue.
Definition: michael_scott_queue.hpp:112
xenium::michael_scott_queue
An unbounded generic lock-free multi-producer/multi-consumer FIFO queue.
Definition: michael_scott_queue.hpp:38
xenium::michael_scott_queue::try_pop
bool try_pop(T &result)
Tries to pop an object from the queue. If the operation is successful, the object will be moved to re...
Definition: michael_scott_queue.hpp:150
xenium::policy::reclaimer
Policy to configure the reclamation scheme to be used.
Definition: policy.hpp:25
xenium::no_backoff
Dummy backoff strategy that does nothing.
Definition: backoff.hpp:16