Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
forward_list.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Containers_LockFreeDataStructures_forward_list_h_
5#define _Stroika_Foundation_Containers_LockFreeDataStructures_forward_list_h_
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <atomic>
10#include <optional>
11
12/**
13 * \file
14 *
15 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
16 *
17 * Initial implementation copied from (so full credit for algorithms)
18 * https://codereview.stackexchange.com/questions/167252/c11-lock-free-collection-similar-to-stdforward-list-follow-up-2
19 * APPENTLY written by https://codereview.stackexchange.com/users/62314/brent
20 * All said about license we MIT licence, which is what Stroika uses, so I assume will be OK.
21 *
22 * TODO:
23 * @todo Review https://en.wikipedia.org/wiki/Non-blocking_linked_list for more ideas on how to improve/fix
24 * this code.
25 * @todo Review https://www.infoq.com/news/2014/10/cpp-lock-free-programming/ to make sure I've handled the
26 * parts he covers - at least - correctly.
27 *
28 * Sutters approach is fairly different. Worth considering, as much simpler. He probably showed all I really
29 * need - the definition of reference, and how that fits with push_front, pop_front... Could implement both ways
30 * with ifdefs?
31 *
32 * @see http://stroika-bugs.sophists.com/browse/STK-723 for details on stuff todo above
33 */
34
35namespace Stroika::Foundation::Containers::LockFreeDataStructures {
36
37 /**
38 * \brief lock-free, threadsafe singly linked list implementation (patterned after std::forward_list) - INTERNALLY SYNCHRONIZED
39 *
40 * similar to std::forward_list, but thread safe and (partially) lock free
41 *
42 * o all methods are thread safe, baring the destructor
43 * o push, insert, emplace, pop, erase, and iterator increment/dereference are constant
44 * o locks that do occur are per-element spin_ locks
45 * o push, insert, emplace, clear, and separate operations are lock free.
46 * o pop and erase are not lock free.
47 * o begin, cbegin, and incrementing iterators are not lock free
48 * o dereferencing iterators is lock free
49 * o uses reference counting to preserve iterators (and values) of removed elements
50 * o iterators of removed elements will increment to end()
51 * o insert_after or emplace_after on a removed iterator will return end() to indicate failure.
52 *
53 * Methods from std::forward_list NYI
54 * o get_allocator & allocators in general (probably doesn't make sense here)
55 * o https://en.cppreference.com/w/cpp/container/forward_list/before_begin
56 * o https://en.cppreference.com/w/cpp/container/forward_list/max_size
57 * o https://en.cppreference.com/w/cpp/container/forward_list/resize
58 * o https://en.cppreference.com/w/cpp/container/forward_list/merge
59 * o https://en.cppreference.com/w/cpp/container/forward_list/splice_after
60 * o https://en.cppreference.com/w/cpp/container/forward_list/remove
61 * o https://en.cppreference.com/w/cpp/container/forward_list/reverse
62 * o https://en.cppreference.com/w/cpp/container/forward_list/unique
63 * o https://en.cppreference.com/w/cpp/container/forward_list/sort
64 * o https://en.cppreference.com/w/cpp/container/forward_list/operator_cmp
65 * o https://en.cppreference.com/w/cpp/container/forward_list/erase2
66 *
67 * Methods from std::forward_list materially wrongly, incompletely implemented
68 * o erase_after incomplete/wrong API
69 *
70 * See Also:
71 * @see https://en.wikipedia.org/wiki/Non-blocking_linked_list
72 *
73 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
74 */
75 template <typename T>
77 private:
78 class node_;
79 template <typename U>
80 class ForwardIterator_;
81
82 public:
83 typedef T value_type;
84 typedef value_type& reference;
85 typedef const value_type& const_reference;
86 typedef value_type* pointer;
87 typedef value_type const* const_pointer;
88 typedef ForwardIterator_<T> iterator;
89 typedef ForwardIterator_<const T> const_iterator;
90
91 public:
92 /**
93 */
94 forward_list ();
95 forward_list (const forward_list& src);
96 forward_list (forward_list&& src) noexcept;
97
98 public:
100
101 public:
102 /**
103 * NYI
104 */
105 nonvirtual forward_list& operator= (const forward_list& rhs) = delete;
106
107 public:
108 /**
109 * @see https://en.cppreference.com/w/cpp/container/forward_list/empty
110 *
111 * lock free
112 */
113 nonvirtual bool empty () const noexcept;
114
115 public:
116 /**
117 * The first node_ is removed before locking the other nodes.
118 * Push will not block.
119 * An iterator incremented externally may move to a value no longer in the list
120 *
121 * \note UNLIKE https://en.cppreference.com/w/cpp/container/forward_list/clear, this method returns the number of elements deleted
122 *
123 * lock free
124 */
125 nonvirtual size_t clear ();
126
127 public:
128 /**
129 * **NOT A METHOD OF std::forward_list<>
130 * NOT lock free
131 * All nodes are locked before removing the first element.
132 * Incrementing an iterator will block, and then result in the end() iterator
133 */
134 nonvirtual size_t locked_clear ();
135
136 public:
137 /**
138 * @see https://en.cppreference.com/w/cpp/container/forward_list/front
139 * <ORIGINAL DOCS SAID 'lock free' - but it calls begin () which orignal docs say NOT lock-free, so unclear but probably NOT lockfree>
140 */
141 nonvirtual reference front ();
142 nonvirtual const_reference front () const;
143
144 public:
145 /**
146 * @see https://en.cppreference.com/w/cpp/container/forward_list/push_front
147 * but returns iterator pointing to element just added
148 *
149 * lock free
150 */
151 nonvirtual iterator push_front (const T& value);
152 nonvirtual iterator push_front (T&& value);
153
154 public:
155 /**
156 * https://en.cppreference.com/w/cpp/container/forward_list/emplace_front
157 * lock free
158 */
159 template <typename... U>
160 nonvirtual reference emplace_front (U&&... params);
161
162 public:
163 /**
164 * @ see https://en.cppreference.com/w/cpp/container/forward_list/pop_front
165 * UNLIKE std version, this returns the value popped as argument, and nullopt if the list was empty.
166 *
167 * **not** lock free
168 */
169 nonvirtual std::optional<T> pop_front ();
170
171 public:
172 /**
173 * @see https://en.cppreference.com/w/cpp/container/forward_list/begin
174 *
175 * **not** lock free
176 */
177 nonvirtual iterator begin ();
178 nonvirtual const_iterator begin () const;
179
180 public:
181 /**
182 * @see https://en.cppreference.com/w/cpp/container/forward_list/begin
183 *
184 * **not** lock free
185 */
186 nonvirtual const_iterator cbegin () const;
187
188 public:
189 /**
190 * @see https://en.cppreference.com/w/cpp/container/forward_list/end
191 *
192 * lock free
193 */
194 nonvirtual iterator end ();
195 nonvirtual const_iterator end () const;
196
197 public:
198 /**
199 * @see https://en.cppreference.com/w/cpp/container/forward_list/end
200 *
201 * lock free
202 */
203 nonvirtual const_iterator cend () const;
204
205 public:
206 /**
207 * @see https://en.cppreference.com/w/cpp/container/forward_list/insert_after
208 *
209 * All overloads lock_free
210 * << was comented only on first overload but appears to apply to all) returns a default constructed iterator if position is no longer valid
211 */
212 nonvirtual iterator insert_after (const_iterator position, T const& value);
213 nonvirtual iterator insert_after (const_iterator position, T&& value);
214 nonvirtual iterator insert_after (const_iterator pos, int count, const T& value);
215 template <typename InputIt>
216 nonvirtual iterator insert_after (const_iterator pos, InputIt first, InputIt last);
217 nonvirtual iterator insert_after (const_iterator pos, std::initializer_list<T> ilist);
218
219 public:
220 /**
221 * @see https://en.cppreference.com/w/cpp/container/forward_list/emplace_after
222 *
223 * lock free
224 */
225 template <typename... U>
226 nonvirtual iterator emplace_after (const_iterator position, U&&... params);
227
228 public:
229 /**
230 * NOT standard forward_list<> funciton but something the original author found useful.
231 * lock free
232 *
233 * all the elements after position are moved to a new forward_list
234 * IMPORTANT: existing iterators will still traverse the separated portion if already within the separated portion
235 */
236 nonvirtual bool separate_after (const_iterator position, forward_list<T>*& result);
237
238 public:
239 /**
240 * DRAFT IMPL - needed for test use
241 * NOT lock free
242 * untested, and unsure if I have off by one issue
243 */
244 nonvirtual bool remove (T value)
245 {
246 for (auto i = begin (); i != this->end (); ++i) {
247 if ((i + 1) != this->end () and *i == value) {
248 T ignored{};
249 this->erase_after (i, &ignored);
250 return true;
251 }
252 }
253 return false;
254 }
255
256 public:
257 /**
258 * @see https://en.cppreference.com/w/cpp/container/forward_list/erase_after
259 *
260 * <<@todo fix - wrong API - sb two overloads and this is neither)
261 *
262 * NOT lock free
263 */
264 nonvirtual bool erase_after (const_iterator position, T* value);
265
266 public:
267 /**
268 * @see https://en.cppreference.com/w/cpp/container/forward_list/swap
269 *
270 * NOT lock free on this, lock free on other
271 */
272 nonvirtual void swap (forward_list& other) noexcept;
273
274 private:
275 // provides a globally unique pointer for the lock/spin sentinels node_
276 // would be nice to use constexpr, but not allowed by gcc with reinterpret_cast
277 static inline node_* const kTerminalSentinel_ = reinterpret_cast<node_*> (1);
278 static inline node_* const kSpinSentinel_ = reinterpret_cast<node_*> (2);
279
280 std::atomic<node_*> fFirst_;
281
282 // (LGP APPEARS TO) return iterator point at element just added
283 // lock free
284 static iterator insert_node_ (std::atomic<node_*>& atomic_ptr, node_* n);
285
286 // lock free, removes all the nodes from *atomic_ptr forward, and returns that node_ with links still intact
287 static node_* separate_ (std::atomic<node_*>& atomic_ptr);
288
289 // NOT lock free
290 // returns true iff it removed something
291 static bool remove_node_ (std::atomic<node_*>& atomic_ptr, T* value);
292
293 // lock free, decrement node_::referenceCount, used for iterator and for prior-node_'s link
294 static void decrement_reference_count_ (node_*& n);
295
296 // lock free, used for iterators and for for prior-node_'s link
297 // return a new "ownership"
298 static node_* increment_reference_count_ (node_* n);
299
300 // fetch the data from node 'n' until we get a value which differs from kSpinSentinel_
301 static node_* spin_get_ (const std::atomic<node_*>& n);
302
303 // lock free, swap the node_ *s in left and right,
304 static void exchange_ (std::atomic<node_*>& left, node_*& right);
305
306 // NOT lock free on left, lock free on right
307 static void exchange_ (std::atomic<node_*>& left, std::atomic<node_*>& right);
308
309 // NOT lock free, set atomic_ptr to spin_ and return the node_ * leaving the node_ locked, unless atomic_ptr is already terminal_ then return terminal_
310 // "ownership" is transferred from atomic_ptr to the return value
311 static node_* owner_lock_ (std::atomic<node_*>& atomic_ptr);
312
313 // lock free, but requires a preceding call to lock, changes atomic_ptr from spin_ to n, sets n to nullptr
314 // "ownership" is transfered from n to atomic_ptr
315 static void owner_unlock_ (std::atomic<node_*>& atomic_ptr, node_*& n);
316
317 // NOT lock free
318 static node_* new_ownership_ (std::atomic<node_*>& atomic_ptr);
319 };
320
321}
322
323namespace std {
324 template <typename T>
327}
328
329/*
330 ********************************************************************************
331 ***************************** Implementation Details ***************************
332 ********************************************************************************
333 */
334#include "forward_list.inl"
335
336#endif /*_Stroika_Foundation_Containers_LockFreeDataStructures_forward_list_h_ */
lock-free, threadsafe singly linked list implementation (patterned after std::forward_list) - INTERNA...
nonvirtual bool erase_after(const_iterator position, T *value)
nonvirtual iterator insert_after(const_iterator position, T const &value)
nonvirtual forward_list & operator=(const forward_list &rhs)=delete
nonvirtual bool separate_after(const_iterator position, forward_list< T > *&result)
nonvirtual iterator emplace_after(const_iterator position, U &&... params)
STL namespace.