Library of Assembled Shared Sources
lock_free_queue.inl
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2022 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43namespace lass
44{
45namespace stde
46{
47
48// --- public --------------------------------------------------------------------------------------
49
50template <typename T, typename A>
51lock_free_queue<T, A>::lock_free_queue():
52 node_allocator_(sizeof(node_t)),
53 value_allocator_(sizeof(value_type))
54{
55 pointer_t tail(make_node(nullptr), 0);
56 head_ = tail;
57 tail_ = tail;
58
59#if defined(__cpp_lib_atomic_is_always_lock_free)
60 static_assert(std::atomic<pointer_t>::is_always_lock_free);
61#else
62 LASS_ENFORCE(head_.is_lock_free());
63 LASS_ENFORCE(tail_.is_lock_free());
64#endif
65}
66
67
68
69template <typename T, typename A>
70lock_free_queue<T, A>::~lock_free_queue()
71{
72 // head node has no value, but it always exists.
73 pointer_t head = head_.load(std::memory_order_acquire);
74 pointer_t node = head->next.load(std::memory_order_relaxed);
75 while (node)
76 {
77 pointer_t next = node->next.load(std::memory_order_relaxed);
78 free_value(node->value.load(std::memory_order_relaxed));
79 free_node(node.get());
80 node = next;
81 }
82 free_node(head.get());
83}
84
85
86
87/** push a value in the back
88 * @arg exception safe: if no node of could be allocatoed, or if copy constructor of x throws,
89 * it fails gracefully.
90 */
91template <typename T, typename A>
92void lock_free_queue<T, A>::push(const value_type& x)
93{
94 value_type* const value = make_value(x);
95 push_value(value);
96}
97
98
99
100/** push a value in the back
101 */
102template <typename T, typename A>
103void lock_free_queue<T, A>::push(value_type&& x)
104{
105 value_type* const value = make_value(std::move(x));
106 push_value(value);
107}
108
109
110
111/** emplace a value in the back
112 */
113template <typename T, typename A>
114template <class... Args>
116{
117 value_type* const value = make_value(std::forward<Args>(args)...);
118 push_value(value);
119}
120
121
122
123/** Try to pop a value from the front and store it in x.
124 * @return false if no element could be popped.
125 */
126template <typename T, typename A>
127bool lock_free_queue<T, A>::pop(value_type& x)
128{
129 while (true)
130 {
131 pointer_t head = head_.load(std::memory_order_acquire);
132 pointer_t tail = tail_.load(std::memory_order_acquire);
133 pointer_t next = head->next.load(std::memory_order_acquire);
134 if (head == head_.load(std::memory_order_acquire))
135 {
136 if (head.get() == tail.get())
137 {
138 if (!next)
139 {
140 return false;
141 }
142 pointer_t new_tail(next.get(), tail.nextTag());
143 tail_.compare_exchange_weak(tail, new_tail);
144 }
145 else
146 {
147 // This is the tricky part ... does 'next' still exist?
148 // In theory, it can be freed by now. But by using
149 // AllocatorConcurrentFreeList, it's guaranteed that at least its memory
150 // is not reclaimed by the OS. It's either sitting unallocated in the
151 // free-list and has its memory preserved, or it's already being
152 // reallocated for a new node.
153 // In both cases, it should be safe to read next->value.
154 // And in both cases, the compare_exchange_strong that follows will
155 // return false before we try to dereference value.
156 //
157 value_type* value = next->value.load(std::memory_order_acquire);
158
159 pointer_t new_head(next.get(), head.nextTag());
160 if (head_.compare_exchange_strong(head, new_head))
161 {
162 try
163 {
164 x = std::move(*value);
165 }
166 catch (...)
167 {
168 free_value(value);
169 free_node(head.get());
170 throw;
171 }
172 free_value(value);
173 free_node(head.get());
174 return true;
175 }
176 }
177 }
178 }
179}
180
181// --- private -------------------------------------------------------------------------------------
182
183template <typename T, typename A>
184template <class... Args>
185typename lock_free_queue<T, A>::value_type*
186lock_free_queue<T, A>::make_value(Args&&... args)
187{
188 void* p = value_allocator_.allocate();
189 try
190 {
191 return new (p) value_type{ std::forward<Args>(args)... };
192 }
193 catch (...)
194 {
195 value_allocator_.deallocate(p);
196 throw;
197 }
198}
199
200
201
202template <typename T, typename A>
203void lock_free_queue<T, A>::free_value(value_type* value)
204{
205 value->~value_type();
206 value_allocator_.deallocate(value);
207}
208
209
210
211template <typename T, typename A>
212void lock_free_queue<T, A>::push_value(value_type* value)
213{
214 node_t* node = 0;
215 try
216 {
217 node = make_node(value);
218 }
219 catch (...)
220 {
221 free_value(value);
222 throw;
223 }
224 pointer_t tail;
225 while (true)
226 {
227 tail = tail_.load(std::memory_order_acquire);
228 pointer_t next = tail->next.load(std::memory_order_acquire);
229 if (tail == tail_.load(std::memory_order_acquire))
230 {
231 if (!next)
232 {
233 pointer_t new_next(node, next.nextTag());
234 if (tail->next.compare_exchange_weak(next, new_next))
235 {
236 break;
237 }
238 }
239 else
240 {
241 pointer_t new_tail(next.get(), tail.nextTag());
242 tail_.compare_exchange_weak(tail, new_tail);
243 }
244 }
245 }
246 pointer_t new_tail(node, tail.nextTag());
247 tail_.compare_exchange_strong(tail, new_tail);
248}
249
250
251
252template <typename T, typename A>
253typename lock_free_queue<T, A>::node_t*
254lock_free_queue<T, A>::make_node(value_type* value)
255{
256 void* p = node_allocator_.allocate();
257 try
258 {
259 return new (p) node_t(value);
260 }
261 catch (...)
262 {
263 node_allocator_.deallocate(p);
264 throw;
265 }
266}
267
268
269
270template <typename T, typename A>
271void lock_free_queue<T, A>::free_node(node_t* node)
272{
273 node->~node_t();
274 node_allocator_.deallocate(node);
275}
276
277
278
279}
280
281}
282
283// EOF
void push(const value_type &x)
push a value in the back
void emplace(Args &&... args)
emplace a value in the back
bool pop(value_type &x)
Try to pop a value from the front and store it in x.
const lass::python::impl::IterNextSlot next("__next__", Py_tp_iternext)
__next__ method (iterator next)
lass extensions to the standard library
Library for Assembled Shared Sources.
Definition config.h:53