Library of Assembled Shared Sources
 
Loading...
Searching...
No Matches
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-2026 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 static_assert(std::atomic<pointer_t>::is_always_lock_free);
60}
61
62
63
64template <typename T, typename A>
65lock_free_queue<T, A>::~lock_free_queue()
66{
67 // head node has no value, but it always exists.
68 pointer_t head = head_.load(std::memory_order_acquire);
69 pointer_t node = head->next.load(std::memory_order_relaxed);
70 while (node)
71 {
72 pointer_t next = node->next.load(std::memory_order_relaxed);
73 free_value(node->value.load(std::memory_order_relaxed));
74 free_node(node.get());
75 node = next;
76 }
77 free_node(head.get());
80
82/** push a value in the back
83 * @arg exception safe: if no node of could be allocatoed, or if copy constructor of x throws,
84 * it fails gracefully.
85 */
86template <typename T, typename A>
87void lock_free_queue<T, A>::push(const value_type& x)
88{
89 value_type* const value = make_value(x);
90 push_value(value);
91}
92
93
94
95/** push a value in the back
96 */
97template <typename T, typename A>
98void lock_free_queue<T, A>::push(value_type&& x)
99{
100 value_type* const value = make_value(std::move(x));
101 push_value(value);
102}
103
104
105
106/** emplace a value in the back
107 */
108template <typename T, typename A>
109template <class... Args>
111{
112 value_type* const value = make_value(std::forward<Args>(args)...);
113 push_value(value);
114}
115
116
117
118/** Try to pop a value from the front and store it in x.
119 * @return false if no element could be popped.
120 */
121template <typename T, typename A>
122bool lock_free_queue<T, A>::pop(value_type& x)
123{
124 while (true)
125 {
126 pointer_t head = head_.load(std::memory_order_acquire);
127 pointer_t tail = tail_.load(std::memory_order_acquire);
128 pointer_t next = head->next.load(std::memory_order_acquire);
129 if (head == head_.load(std::memory_order_acquire))
130 {
131 if (head.get() == tail.get())
132 {
133 if (!next)
134 {
135 return false;
136 }
137 pointer_t new_tail(next.get(), tail.nextTag());
138 tail_.compare_exchange_weak(tail, new_tail);
139 }
140 else
141 {
142 // This is the tricky part ... does 'next' still exist?
143 // In theory, it can be freed by now. But by using
144 // AllocatorConcurrentFreeList, it's guaranteed that at least its memory
145 // is not reclaimed by the OS. It's either sitting unallocated in the
146 // free-list and has its memory preserved, or it's already being
147 // reallocated for a new node.
148 // In both cases, it should be safe to read next->value.
149 // And in both cases, the compare_exchange_strong that follows will
150 // return false before we try to dereference value.
151 //
152 value_type* value = next->value.load(std::memory_order_acquire);
153
154 pointer_t new_head(next.get(), head.nextTag());
155 if (head_.compare_exchange_strong(head, new_head))
156 {
157 try
158 {
159 x = std::move(*value);
160 }
161 catch (...)
162 {
163 free_value(value);
164 free_node(head.get());
165 throw;
166 }
167 free_value(value);
168 free_node(head.get());
169 return true;
170 }
171 }
172 }
173 }
174}
175
176// --- private -------------------------------------------------------------------------------------
177
178template <typename T, typename A>
179template <class... Args>
180typename lock_free_queue<T, A>::value_type*
181lock_free_queue<T, A>::make_value(Args&&... args)
182{
183 void* p = value_allocator_.allocate();
184 try
185 {
186 return new (p) value_type{ std::forward<Args>(args)... };
187 }
188 catch (...)
189 {
190 value_allocator_.deallocate(p);
191 throw;
192 }
193}
194
195
196
197template <typename T, typename A>
198void lock_free_queue<T, A>::free_value(value_type* value)
199{
200 value->~value_type();
201 value_allocator_.deallocate(value);
202}
203
204
205
206template <typename T, typename A>
207void lock_free_queue<T, A>::push_value(value_type* value)
208{
209 node_t* node = 0;
210 try
211 {
212 node = make_node(value);
213 }
214 catch (...)
215 {
216 free_value(value);
217 throw;
218 }
219 pointer_t tail;
220 while (true)
221 {
222 tail = tail_.load(std::memory_order_acquire);
223 pointer_t next = tail->next.load(std::memory_order_acquire);
224 if (tail == tail_.load(std::memory_order_acquire))
225 {
226 if (!next)
227 {
228 pointer_t new_next(node, next.nextTag());
229 if (tail->next.compare_exchange_weak(next, new_next))
230 {
231 break;
232 }
233 }
234 else
235 {
236 pointer_t new_tail(next.get(), tail.nextTag());
237 tail_.compare_exchange_weak(tail, new_tail);
238 }
239 }
240 }
241 pointer_t new_tail(node, tail.nextTag());
242 tail_.compare_exchange_strong(tail, new_tail);
243}
244
245
246
247template <typename T, typename A>
248typename lock_free_queue<T, A>::node_t*
249lock_free_queue<T, A>::make_node(value_type* value)
250{
251 void* p = node_allocator_.allocate();
252 try
253 {
254 return new (p) node_t(value);
255 }
256 catch (...)
257 {
258 node_allocator_.deallocate(p);
259 throw;
260 }
261}
262
263
264
265template <typename T, typename A>
266void lock_free_queue<T, A>::free_node(node_t* node)
267{
268 node->~node_t();
269 node_allocator_.deallocate(node);
270}
271
272
273
274}
275
276}
277
278// 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