Jamba C++ API 7.5.0
Loading...
Searching...
No Matches
Concurrent.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2018-2019 pongasoft
3 *
4 * Licensed under the Apache License, Version 2.0 or the MIT license,
5 * at your option. You may not use this file except in compliance with
6 * one of these licenses. You may obtain copies of the licenses at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 * https://opensource.org/licenses/MIT
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 *
17 * @author Yan Pujante
18 */
19#ifndef __PONGASOFT_UTILS_CONCURRENT_CONCURRENT_H__
20#define __PONGASOFT_UTILS_CONCURRENT_CONCURRENT_H__
21
22#include "SpinLock.h"
23
24#include <memory>
25
26namespace pongasoft {
27namespace Utils {
28namespace Concurrent {
29
30/*
31 * One of the big issue in the VST world comes from the fact that the processing thread maintains the state but it
32 * is being accessed/changed by the UI thread (AudioEffect::setState and AudioEffect::getState). Moreover if the
33 * processing thread needs to send a message to the UI thread this must happen in a timer (running in the UI thread)
34 * meaning the processing thread needs to have a way to communicate the content of the message to the timer in a
35 * thread safe way. See thread https://sdk.steinberg.net/viewtopic.php?f=4&t=516 for discussion.
36 *
37 * The 2 primitives required are an atomic value (for getState) and a queue with one element (where the element in the
38 * queue can be updated if not popped yet) (for setState / timer message)
39 *
40 * The golden rules of real time audio programming are not to use locks or memory allocation. Here are 2
41 * implementations with different tradeoffs.
42 *
43 * The LockFree namespace implements a version that does not use locks or allocate memory at runtime (only when
44 * the classes are created). The tradeoff is that it uses more memory (3 instances of T) and it is only thread
45 * safe when there is a single thread calling 'get' (resp 'pop') and another single thread calling 'set' (rep 'push').
46 *
47 * The WithSpinLock namespace a version which uses a very lightweight lock: a user space spin lock. The SpinLock
48 * implementation is not allocating any memory in any thread and is relying on the std::atomic_flag concept which is
49 * guaranteed to be lock free. It also does not make any system calls. The tradeoff is that the queue and atomic
50 * value do lock for the duration of the copy of T. The advantages are less memory use and fully multi thread safe.
51 */
52
53//------------------------------------------------------------------------
54// Lock Free Implementation of AtomicValue and SingleQueueElement
55//------------------------------------------------------------------------
56namespace LockFree {
65template<typename T>
67{
68public:
69 // wraps a unique pointer of type T and whether it is a new value or not
70 struct Element
71 {
72 Element(std::unique_ptr<T> iElement, bool iNew) noexcept : fElement{std::move(iElement)}, fNew{iNew} {}
73
74 std::unique_ptr<T> fElement;
75 bool fNew;
76 };
77
78public:
79 // Constructor
80 SingleElementStorage(std::unique_ptr<T> iElement, bool iIsEmpty) noexcept :
81 fSingleElement{new Element(std::move(iElement), !iIsEmpty)}
82 {}
83
84 // Destructor - Deletes the element created in the constructor
86 {
87 delete fSingleElement.exchange(nullptr);
88 }
89
90 // isEmpty
91 inline bool isEmpty() const
92 {
93 return !fSingleElement.load()->fNew;
94 }
95
100 bool __isLockFree() const { return fSingleElement.is_lock_free(); }
101
102protected:
107 std::unique_ptr<Element> store(std::unique_ptr<Element> iElement)
108 {
109 iElement->fNew = true;
110 iElement.reset(fSingleElement.exchange(iElement.release()));
111 return std::move(iElement);
112 }
113
125 std::unique_ptr<Element> load(std::unique_ptr<Element> iElement)
126 {
127 iElement->fNew = false;
128 iElement.reset(fSingleElement.exchange(iElement.release()));
129 return std::move(iElement);
130 }
131
132 // __newT => create a new T by using copy constructor
133 std::unique_ptr<T> __newT() const { return std::make_unique<T>(*(fSingleElement.load()->fElement)); }
134
135 // __newElement
136 std::unique_ptr<Element> __newElement() const { return std::make_unique<Element>(std::move(__newT()), false); }
137
138private:
139 // using a std::atomic on a pointer which should be lock free (check __isLockFree for sanity check!)
140 std::atomic<Element *> fSingleElement;
141};
142
148template<typename T>
150{
151public:
152 // Constructor
154 SingleElementStorage<T>{std::make_unique<T>(), true},
157 {}
158
161 explicit SingleElementQueue(std::unique_ptr<T> iElement, bool iIsEmpty = false) :
162 SingleElementStorage<T>{std::move(iElement), iIsEmpty},
165 {
166 }
167
168 //------------------------------------------------------------------------------------------------------------
169 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
170 //
171 // All the following methods (pop and last) should be called in a single thread
172 //
173 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
174 //------------------------------------------------------------------------------------------------------------
178 T *pop()
179 {
180 if(!this->isEmpty())
181 {
182 fPopValue = std::move(SingleElementStorage<T>::load(std::move(fPopValue)));
183 }
184
185 if(fPopValue->fNew)
186 {
187 fPopValue->fNew = false;
188 return fPopValue->fElement.get();
189 }
190
191 return nullptr;
192 };
193
197 bool pop(T &oElement)
198 {
199 auto element = pop();
200 if(element)
201 {
202 oElement = *element;
203 return true;
204 }
205
206 return false;
207 }
208
212 T const *last() const
213 {
214 return fPopValue->fElement.get();
215 };
216
217
221 void last(T &oElement) const
222 {
223 oElement = *last();
224 }
225
229 T const *popOrLast()
230 {
231 auto element = pop();
232
233 if(element)
234 return element;
235 else
236 return fPopValue->fElement.get();
237 };
238
239
243 void popOrLast(T &oElement)
244 {
245 oElement = *popOrLast();
246 }
247
248 //------------------------------------------------------------------------------------------------------------
249 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
250 //
251 // All the following methods (push and updateAndPush) should be called in a single thread
252 //
253 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
254 //------------------------------------------------------------------------------------------------------------
255
259 void push(T const &iElement)
260 {
261 *(fPushValue->fElement.get()) = iElement;
262 pushValue();
263 }
264
268 void push(T const *iElement)
269 {
270 *(fPushValue->fElement.get()) = *iElement;
271 pushValue();
272 }
273
278 template<class ElementModifier>
279 void updateAndPush(ElementModifier const &iElementModifier)
280 {
281 iElementModifier(fPushValue->fElement.get());
282 pushValue();
283 }
284
289 template<class ElementModifier>
290 bool updateAndPushIf(ElementModifier const &iElementModifier)
291 {
292 if(iElementModifier(fPushValue->fElement.get()))
293 {
294 pushValue();
295 return true;
296 }
297 return false;
298 }
299private:
301 {
303 }
304
305private:
307
308 std::unique_ptr<Element> fPopValue;
309 std::unique_ptr<Element> fPushValue;
310};
311
317template<typename T>
319{
320public:
321 // Constructor - needs a value for initalizing the object
322 explicit AtomicValue(std::unique_ptr<T> iValue) :
323 SingleElementStorage<T>{std::move(iValue), false},
326 {}
327
328 //------------------------------------------------------------------------------------------------------------
329 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
330 //
331 // All the following methods (get) should be called in a single thread
332 //
333 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
334 //------------------------------------------------------------------------------------------------------------
335
339 T const *get()
340 {
341 if(!this->isEmpty())
342 {
343 fGetValue = std::move(SingleElementStorage<T>::load(std::move(fGetValue)));
344 }
345
346 return fGetValue->fElement.get();
347 };
348
353 {
354 return *get();
355 }
356
360 void get(T &oElement)
361 {
362 oElement = *get();
363 };
364
368 void get(T *oElement)
369 {
370 *oElement = *get();
371 };
372
373 //------------------------------------------------------------------------------------------------------------
374 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
375 //
376 // All the following methods (set / update) should be called in a single thread
377 //
378 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
379 //------------------------------------------------------------------------------------------------------------
380
384 void set(T const &iValue)
385 {
386 *(fSetValue->fElement.get()) = iValue;
387 fSetValue = std::move(SingleElementStorage<T>::store(std::move(fSetValue)));
388 }
389
393 void set(T const *iValue)
394 {
395 *(fSetValue->fElement.get()) = *iValue;
396 fSetValue = std::move(SingleElementStorage<T>::store(std::move(fSetValue)));
397 }
398
403 template<class ElementModifier>
404 void update(ElementModifier const &iElementModifier)
405 {
406 iElementModifier(fSetValue->fElement.get());
407 fSetValue = std::move(SingleElementStorage<T>::store(std::move(fSetValue)));
408 }
409
414 template<class ElementModifier>
415 bool updateIf(ElementModifier const &iElementModifier)
416 {
417 if(iElementModifier(fSetValue->fElement.get()))
418 {
419 fSetValue = std::move(SingleElementStorage<T>::store(std::move(fSetValue)));
420 return true;
421 }
422
423 return false;
424 }
425
426private:
428
429 std::unique_ptr<Element> fGetValue;
430 std::unique_ptr<Element> fSetValue;
431};
432}
433
437namespace WithSpinLock {
438
445template<typename T>
447{
448public:
449 SingleElementQueue() : fSingleElement{std::make_unique<T>()}, fIsEmpty{true}, fSpinLock{}
450 {}
451
456 explicit SingleElementQueue(std::unique_ptr<T> iFirstElement,
457 bool iIsEmpty = false) :
458 fSingleElement{std::move(iFirstElement)},
459 fIsEmpty{iIsEmpty},
460 fSpinLock{}
461 {}
462
469 bool isEmpty() const
470 {
471 auto lock = fSpinLock.acquire();
472 return fIsEmpty;
473 }
474
482 bool pop(T &oElement)
483 {
484 auto lock = fSpinLock.acquire();
485 if(fIsEmpty)
486 return false;
487
488 oElement = *fSingleElement;
489 fIsEmpty = true;
490
491 return true;
492 };
493
501 bool pop(T *oElement)
502 {
503 auto lock = fSpinLock.acquire();
504 if(fIsEmpty)
505 return false;
506
507 *oElement = *fSingleElement;
508 fIsEmpty = true;
509
510 return true;
511 };
512
513
518 void push(T const &iElement)
519 {
520 auto lock = fSpinLock.acquire();
521 *fSingleElement = iElement;
522 fIsEmpty = false;
523 }
524
529 void push(T const *iElement)
530 {
531 auto lock = fSpinLock.acquire();
532 *fSingleElement = *iElement;
533 fIsEmpty = false;
534 }
535
536private:
537 std::unique_ptr<T> fSingleElement;
540};
541
547template<typename T>
549{
550public:
551 explicit AtomicValue(std::unique_ptr<T> iValue) : fValue{std::move(iValue)}, fSpinLock{} {}
552
553 explicit AtomicValue(T const &iValue) : fValue{std::make_unique<T>(iValue)}, fSpinLock{} {}
554
559 T get()
560 {
561 auto lock = fSpinLock.acquire();
562 return *fValue;
563 };
564
569 void get(T &oElement)
570 {
571 auto lock = fSpinLock.acquire();
572 oElement = *fValue;
573 };
574
579 void get(T *oElement)
580 {
581 auto lock = fSpinLock.acquire();
582 *oElement = *fValue;
583 };
584
588 void set(T const &iValue)
589 {
590 auto lock = fSpinLock.acquire();
591 *fValue = iValue;
592 }
593
597 void set(T const *iValue)
598 {
599 auto lock = fSpinLock.acquire();
600 *fValue = *iValue;
601 }
602
603private:
604 std::unique_ptr<T> fValue;
606};
607
608
609}
610}
611}
612}
613
614#endif // __PONGASOFT_UTILS_CONCURRENT_CONCURRENT_H__
A simple implementation of a spin lock using the std::atomic_flag which is guaranteed to be atomic an...
Definition SpinLock.h:34
AtomicValue(std::unique_ptr< T > iValue)
Definition Concurrent.h:322
void set(T const *iValue)
Copy the value to make it accessible to get.
Definition Concurrent.h:393
void get(T *oElement)
Copy the value to *oElement.
Definition Concurrent.h:368
T const * get()
Definition Concurrent.h:339
bool updateIf(ElementModifier const &iElementModifier)
Use this flavor to avoid copy.
Definition Concurrent.h:415
void get(T &oElement)
Copy the value to oElement.
Definition Concurrent.h:360
typename SingleElementStorage< T >::Element Element
Definition Concurrent.h:427
std::unique_ptr< Element > fGetValue
Definition Concurrent.h:429
void update(ElementModifier const &iElementModifier)
Use this flavor to avoid copy.
Definition Concurrent.h:404
std::unique_ptr< Element > fSetValue
Definition Concurrent.h:430
void set(T const &iValue)
Copy the value to make it accessible to get.
Definition Concurrent.h:384
bool pop(T &oElement)
Copy the popped value to oElement and return true when there is a new value otherwise do nothing and ...
Definition Concurrent.h:197
void last(T &oElement) const
Copy the last value that was popped to oElement.
Definition Concurrent.h:221
T const * popOrLast()
Definition Concurrent.h:229
void updateAndPush(ElementModifier const &iElementModifier)
Use this flavor of push to avoid copy.
Definition Concurrent.h:279
void push(T const &iElement)
Pushes (a copy of) iElement in the queue.
Definition Concurrent.h:259
typename SingleElementStorage< T >::Element Element
Definition Concurrent.h:306
SingleElementQueue(std::unique_ptr< T > iElement, bool iIsEmpty=false)
This constructor should be used if T does not provide an empty constructor.
Definition Concurrent.h:161
std::unique_ptr< Element > fPopValue
Definition Concurrent.h:308
bool updateAndPushIf(ElementModifier const &iElementModifier)
Use this flavor of push to avoid copy.
Definition Concurrent.h:290
T const * last() const
Definition Concurrent.h:212
std::unique_ptr< Element > fPushValue
Definition Concurrent.h:309
void push(T const *iElement)
Pushes (a copy of) *iElement in the queue.
Definition Concurrent.h:268
void popOrLast(T &oElement)
Copy either the new value (if there is one) or the last value that was popped to oElement.
Definition Concurrent.h:243
SingleElementStorage(std::unique_ptr< T > iElement, bool iIsEmpty) noexcept
Definition Concurrent.h:80
std::unique_ptr< Element > load(std::unique_ptr< Element > iElement)
Loads an element from storage.
Definition Concurrent.h:125
bool __isLockFree() const
Used (from test) to make sure that it is a lock free implementation.
Definition Concurrent.h:100
std::unique_ptr< Element > store(std::unique_ptr< Element > iElement)
Stores an element in the storage.
Definition Concurrent.h:107
bool isEmpty() const
Definition Concurrent.h:91
std::unique_ptr< T > __newT() const
Definition Concurrent.h:133
std::unique_ptr< Element > __newElement() const
Definition Concurrent.h:136
std::atomic< Element * > fSingleElement
Definition Concurrent.h:140
SpinLock fSpinLock
Definition Concurrent.h:605
AtomicValue(std::unique_ptr< T > iValue)
Definition Concurrent.h:551
void set(T const *iValue)
Updates the current value with the provided one.
Definition Concurrent.h:597
std::unique_ptr< T > fValue
Definition Concurrent.h:604
void get(T *oElement)
Returns the "current" value.
Definition Concurrent.h:579
void get(T &oElement)
Returns the "current" value.
Definition Concurrent.h:569
AtomicValue(T const &iValue)
Definition Concurrent.h:553
void set(T const &iValue)
Updates the current value with the provided one.
Definition Concurrent.h:588
T get()
Returns the "current" value.
Definition Concurrent.h:559
bool pop(T &oElement)
Returns the single element in the queue if there is one.
Definition Concurrent.h:482
std::unique_ptr< T > fSingleElement
Definition Concurrent.h:537
void push(T const &iElement)
Pushes one element in the queue.
Definition Concurrent.h:518
bool pop(T *oElement)
Returns the single element in the queue if there is one.
Definition Concurrent.h:501
void push(T const *iElement)
Pushes one element in the queue.
Definition Concurrent.h:529
SingleElementQueue(std::unique_ptr< T > iFirstElement, bool iIsEmpty=false)
This constructor can be used to add one element to the queue right away or when there is no empty con...
Definition Concurrent.h:456
bool isEmpty() const
Note that although this api is thread safe, it will only report the state of the queue at the moment ...
Definition Concurrent.h:469
Definition Concurrent.h:56
The purpose of this namespace is to emphasize the fact that the implementation is using a spinlock.
Definition Concurrent.h:437
Definition Concurrent.h:28
Definition CircularBuffer.h:26
Definition Clock.h:23
Element(std::unique_ptr< T > iElement, bool iNew) noexcept
Definition Concurrent.h:72
std::unique_ptr< T > fElement
Definition Concurrent.h:74