Jamba C++ API  4.1.0
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 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  *
16  * @author Yan Pujante
17  */
18 #ifndef __PONGASOFT_UTILS_CONCURRENT_CONCURRENT_H__
19 #define __PONGASOFT_UTILS_CONCURRENT_CONCURRENT_H__
20 
21 #include "SpinLock.h"
22 
23 #include <memory>
24 
25 namespace pongasoft {
26 namespace Utils {
27 namespace Concurrent {
28 
29 /*
30  * One of the big issue in the VST world comes from the fact that the processing thread maintains the state but it
31  * is being accessed/changed by the UI thread (AudioEffect::setState and AudioEffect::getState). Moreover if the
32  * processing thread needs to send a message to the UI thread this must happen in a timer (running in the UI thread)
33  * meaning the processing thread needs to have a way to communicate the content of the message to the timer in a
34  * thread safe way. See thread https://sdk.steinberg.net/viewtopic.php?f=4&t=516 for discussion.
35  *
36  * The 2 primitives required are an atomic value (for getState) and a queue with one element (where the element in the
37  * queue can be updated if not popped yet) (for setState / timer message)
38  *
39  * The golden rules of real time audio programming are not to use locks or memory allocation. Here are 2
40  * implementations with different tradeoffs.
41  *
42  * The LockFree namespace implements a version that does not use locks or allocate memory at runtime (only when
43  * the classes are created). The tradeoff is that it uses more memory (3 instances of T) and it is only thread
44  * safe when there is a single thread calling 'get' (resp 'pop') and another single thread calling 'set' (rep 'push').
45  *
46  * The WithSpinLock namespace a version which uses a very lightweight lock: a user space spin lock. The SpinLock
47  * implementation is not allocating any memory in any thread and is relying on the std::atomic_flag concept which is
48  * guaranteed to be lock free. It also does not make any system calls. The tradeoff is that the queue and atomic
49  * value do lock for the duration of the copy of T. The advantages are less memory use and fully multi thread safe.
50  */
51 
52 //------------------------------------------------------------------------
53 // Lock Free Implementation of AtomicValue and SingleQueueElement
54 //------------------------------------------------------------------------
55 namespace LockFree {
64 template<typename T>
66 {
67 public:
68  // wraps a unique pointer of type T and whether it is a new value or not
69  struct Element
70  {
71  Element(std::unique_ptr<T> iElement, bool iNew) noexcept : fElement{std::move(iElement)}, fNew{iNew} {}
72 
73  std::unique_ptr<T> fElement;
74  bool fNew;
75  };
76 
77 public:
78  // Constructor
79  SingleElementStorage(std::unique_ptr<T> iElement, bool iIsEmpty) noexcept :
80  fSingleElement{new Element(std::move(iElement), !iIsEmpty)}
81  {}
82 
83  // Destructor - Deletes the element created in the constructor
85  {
86  delete fSingleElement.exchange(nullptr);
87  }
88 
89  // isEmpty
90  inline bool isEmpty() const
91  {
92  return !fSingleElement.load()->fNew;
93  }
94 
99  bool __isLockFree() const { return fSingleElement.is_lock_free(); }
100 
101 protected:
106  std::unique_ptr<Element> store(std::unique_ptr<Element> iElement)
107  {
108  iElement->fNew = true;
109  iElement.reset(fSingleElement.exchange(iElement.release()));
110  return std::move(iElement);
111  }
112 
124  std::unique_ptr<Element> load(std::unique_ptr<Element> iElement)
125  {
126  iElement->fNew = false;
127  iElement.reset(fSingleElement.exchange(iElement.release()));
128  return std::move(iElement);
129  }
130 
131  // __newT => create a new T by using copy constructor
132  std::unique_ptr<T> __newT() const { return std::make_unique<T>(*(fSingleElement.load()->fElement)); }
133 
134  // __newElement
135  std::unique_ptr<Element> __newElement() const { return std::make_unique<Element>(std::move(__newT()), false); }
136 
137 private:
138  // using a std::atomic on a pointer which should be lock free (check __isLockFree for sanity check!)
139  std::atomic<Element *> fSingleElement;
140 };
141 
147 template<typename T>
149 {
150 public:
151  // Constructor
153  SingleElementStorage<T>{std::make_unique<T>(), true},
156  {}
157 
160  explicit SingleElementQueue(std::unique_ptr<T> iElement, bool iIsEmpty = false) :
161  SingleElementStorage<T>{std::move(iElement), iIsEmpty},
164  {
165  }
166 
167  //------------------------------------------------------------------------------------------------------------
168  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
169  //
170  // All the following methods (pop and last) should be called in a single thread
171  //
172  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
173  //------------------------------------------------------------------------------------------------------------
177  T *pop()
178  {
179  if(!this->isEmpty())
180  {
181  fPopValue = std::move(SingleElementStorage<T>::load(std::move(fPopValue)));
182  }
183 
184  if(fPopValue->fNew)
185  {
186  fPopValue->fNew = false;
187  return fPopValue->fElement.get();
188  }
189 
190  return nullptr;
191  };
192 
196  bool pop(T &oElement)
197  {
198  auto element = pop();
199  if(element)
200  {
201  oElement = *element;
202  return true;
203  }
204 
205  return false;
206  }
207 
211  T const *last() const
212  {
213  return fPopValue->fElement.get();
214  };
215 
216 
220  void last(T &oElement) const
221  {
222  oElement = *last();
223  }
224 
228  T const *popOrLast()
229  {
230  auto element = pop();
231 
232  if(element)
233  return element;
234  else
235  return fPopValue->fElement.get();
236  };
237 
238 
242  void popOrLast(T &oElement)
243  {
244  oElement = *popOrLast();
245  }
246 
247  //------------------------------------------------------------------------------------------------------------
248  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
249  //
250  // All the following methods (push and updateAndPush) should be called in a single thread
251  //
252  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
253  //------------------------------------------------------------------------------------------------------------
254 
258  void push(T const &iElement)
259  {
260  *(fPushValue->fElement.get()) = iElement;
261  pushValue();
262  }
263 
267  void push(T const *iElement)
268  {
269  *(fPushValue->fElement.get()) = *iElement;
270  pushValue();
271  }
272 
277  template<class ElementModifier>
278  void updateAndPush(ElementModifier const &iElementModifier)
279  {
280  iElementModifier(fPushValue->fElement.get());
281  pushValue();
282  }
283 
288  template<class ElementModifier>
289  bool updateAndPushIf(ElementModifier const &iElementModifier)
290  {
291  if(iElementModifier(fPushValue->fElement.get()))
292  {
293  pushValue();
294  return true;
295  }
296  return false;
297  }
298 private:
299  void pushValue()
300  {
301  fPushValue = std::move(SingleElementStorage<T>::store(std::move(fPushValue)));
302  }
303 
304 private:
306 
307  std::unique_ptr<Element> fPopValue;
308  std::unique_ptr<Element> fPushValue;
309 };
310 
316 template<typename T>
318 {
319 public:
320  // Constructor - needs a value for initalizing the object
321  explicit AtomicValue(std::unique_ptr<T> iValue) :
322  SingleElementStorage<T>{std::move(iValue), false},
325  {}
326 
327  //------------------------------------------------------------------------------------------------------------
328  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
329  //
330  // All the following methods (get) should be called in a single thread
331  //
332  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
333  //------------------------------------------------------------------------------------------------------------
334 
338  T const *get()
339  {
340  if(!this->isEmpty())
341  {
342  fGetValue = std::move(SingleElementStorage<T>::load(std::move(fGetValue)));
343  }
344 
345  return fGetValue->fElement.get();
346  };
347 
352  {
353  return *get();
354  }
355 
359  void get(T &oElement)
360  {
361  oElement = *get();
362  };
363 
367  void get(T *oElement)
368  {
369  *oElement = *get();
370  };
371 
372  //------------------------------------------------------------------------------------------------------------
373  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
374  //
375  // All the following methods (set / update) should be called in a single thread
376  //
377  // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
378  //------------------------------------------------------------------------------------------------------------
379 
383  void set(T const &iValue)
384  {
385  *(fSetValue->fElement.get()) = iValue;
386  fSetValue = std::move(SingleElementStorage<T>::store(std::move(fSetValue)));
387  }
388 
392  void set(T const *iValue)
393  {
394  *(fSetValue->fElement.get()) = *iValue;
395  fSetValue = std::move(SingleElementStorage<T>::store(std::move(fSetValue)));
396  }
397 
402  template<class ElementModifier>
403  void update(ElementModifier const &iElementModifier)
404  {
405  iElementModifier(fSetValue->fElement.get());
406  fSetValue = std::move(SingleElementStorage<T>::store(std::move(fSetValue)));
407  }
408 
413  template<class ElementModifier>
414  bool updateIf(ElementModifier const &iElementModifier)
415  {
416  if(iElementModifier(fSetValue->fElement.get()))
417  {
418  fSetValue = std::move(SingleElementStorage<T>::store(std::move(fSetValue)));
419  return true;
420  }
421 
422  return false;
423  }
424 
425 private:
427 
428  std::unique_ptr<Element> fGetValue;
429  std::unique_ptr<Element> fSetValue;
430 };
431 }
432 
436 namespace WithSpinLock {
437 
444 template<typename T>
446 {
447 public:
448  SingleElementQueue() : fSingleElement{std::make_unique<T>()}, fIsEmpty{true}, fSpinLock{}
449  {}
450 
455  explicit SingleElementQueue(std::unique_ptr<T> iFirstElement,
456  bool iIsEmpty = false) :
457  fSingleElement{std::move(iFirstElement)},
458  fIsEmpty{iIsEmpty},
459  fSpinLock{}
460  {}
461 
468  bool isEmpty() const
469  {
470  auto &spinLock = const_cast<SpinLock &>(fSpinLock);
471  auto lock = spinLock.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 
536 private:
537  std::unique_ptr<T> fSingleElement;
538  bool fIsEmpty;
540 };
541 
547 template<typename T>
549 {
550 public:
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 
603 private:
604  std::unique_ptr<T> fValue;
606 };
607 
608 
609 }
610 }
611 }
612 }
613 
614 #endif // __PONGASOFT_UTILS_CONCURRENT_CONCURRENT_H__
std::unique_ptr< T > fValue
Definition: Concurrent.h:604
std::unique_ptr< Element > load(std::unique_ptr< Element > iElement)
Loads an element from storage.
Definition: Concurrent.h:124
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:455
std::unique_ptr< T > fElement
Definition: Concurrent.h:73
A simple implementation of a spin lock using the std::atomic_flag which is guaranteed to be atomic an...
Definition: SpinLock.h:32
std::unique_ptr< Element > __newElement() const
Definition: Concurrent.h:135
This is the lock free version of the AtomicValue.
Definition: Concurrent.h:317
bool isEmpty() const
Definition: Concurrent.h:90
Lock acquire()
Definition: SpinLock.h:74
void get(T *oElement)
Copy the value to *oElement.
Definition: Concurrent.h:367
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:196
This class encapsulates a single atomic value.
Definition: Concurrent.h:548
Definition: Clock.h:22
T const * get()
Definition: Concurrent.h:338
SingleElementStorage(std::unique_ptr< T > iElement, bool iIsEmpty) noexcept
Definition: Concurrent.h:79
T const * last() const
Definition: Concurrent.h:211
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:468
void push(T const &iElement)
Pushes one element in the queue.
Definition: Concurrent.h:518
void get(T *oElement)
Returns the "current" value.
Definition: Concurrent.h:579
AtomicValue(std::unique_ptr< T > iValue)
Definition: Concurrent.h:321
void get(T &oElement)
Returns the "current" value.
Definition: Concurrent.h:569
bool updateAndPushIf(ElementModifier const &iElementModifier)
Use this flavor of push to avoid copy.
Definition: Concurrent.h:289
void push(T const &iElement)
Pushes (a copy of) iElement in the queue.
Definition: Concurrent.h:258
AtomicValue(std::unique_ptr< T > iValue)
Definition: Concurrent.h:551
std::unique_ptr< Element > fSetValue
Definition: Concurrent.h:429
void push(T const *iElement)
Pushes (a copy of) *iElement in the queue.
Definition: Concurrent.h:267
SpinLock fSpinLock
Definition: Concurrent.h:605
void get(T &oElement)
Copy the value to oElement.
Definition: Concurrent.h:359
T const * popOrLast()
Definition: Concurrent.h:228
Element(std::unique_ptr< T > iElement, bool iNew) noexcept
Definition: Concurrent.h:71
void update(ElementModifier const &iElementModifier)
Use this flavor to avoid copy.
Definition: Concurrent.h:403
void set(T const *iValue)
Copy the value to make it accessible to get.
Definition: Concurrent.h:392
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:160
void set(T const &iValue)
Updates the current value with the provided one.
Definition: Concurrent.h:588
AtomicValue(T const &iValue)
Definition: Concurrent.h:553
std::atomic< Element * > fSingleElement
Definition: Concurrent.h:139
std::unique_ptr< T > fSingleElement
Definition: Concurrent.h:537
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:242
std::unique_ptr< Element > fPushValue
Definition: Concurrent.h:308
std::unique_ptr< Element > fPopValue
Definition: Concurrent.h:307
This class implements a queue which has at most 1 element (0 or 1).
Definition: Concurrent.h:445
void set(T const &iValue)
Copy the value to make it accessible to get.
Definition: Concurrent.h:383
bool updateIf(ElementModifier const &iElementModifier)
Use this flavor to avoid copy.
Definition: Concurrent.h:414
std::unique_ptr< Element > fGetValue
Definition: Concurrent.h:428
typename SingleElementStorage< pongasoft::VST::NormalizedState >::Element Element
Definition: Concurrent.h:426
void updateAndPush(ElementModifier const &iElementModifier)
Use this flavor of push to avoid copy.
Definition: Concurrent.h:278
std::unique_ptr< T > __newT() const
Definition: Concurrent.h:132
void last(T &oElement) const
Copy the last value that was popped to oElement.
Definition: Concurrent.h:220
typename SingleElementStorage< pongasoft::VST::NormalizedState >::Element Element
Definition: Concurrent.h:305
void push(T const *iElement)
Pushes one element in the queue.
Definition: Concurrent.h:529
bool pop(T *oElement)
Returns the single element in the queue if there is one.
Definition: Concurrent.h:501
void set(T const *iValue)
Updates the current value with the provided one.
Definition: Concurrent.h:597
This is the lock free version of the SingleElementQueue.
Definition: Concurrent.h:148
bool pop(T &oElement)
Returns the single element in the queue if there is one.
Definition: Concurrent.h:482
bool __isLockFree() const
Used (from test) to make sure that it is a lock free implementation.
Definition: Concurrent.h:99
T get()
Returns the "current" value.
Definition: Concurrent.h:559
This (internal) class stores a single element.
Definition: Concurrent.h:65
std::unique_ptr< Element > store(std::unique_ptr< Element > iElement)
Stores an element in the storage.
Definition: Concurrent.h:106