/[svn]/linuxsampler/trunk/src/common/SynchronizedConfig.h
ViewVC logotype

Contents of /linuxsampler/trunk/src/common/SynchronizedConfig.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3034 - (show annotations) (download) (as text)
Mon Oct 31 00:05:00 2016 UTC (7 years, 4 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 16318 byte(s)
* Fixed a bunch of minor issues (mostly compiler warnings).
* Bumped version (2.0.0.svn31).

1 /***************************************************************************
2 * *
3 * Copyright (C) 2006-2016 Andreas Persson *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software *
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, *
18 * MA 02110-1301 USA *
19 ***************************************************************************/
20
21 #ifndef SYNCHRONIZEDCONFIG_H
22 #define SYNCHRONIZEDCONFIG_H
23
24 #include <set>
25 #include <unistd.h>
26 #include "lsatomic.h"
27 #include "Mutex.h"
28
29 namespace LinuxSampler {
30
31 /**
32 * Thread-safe management of configuration data, where the data is
33 * updated by a single non real time thread and read by a number
34 * of real time threads.
35 *
36 * The synchronization is achieved by using two instances of the
37 * configuration data. The non real time thread gets access to the
38 * instance not currently in use by the real time threads by
39 * calling GetConfigForUpdate(). After the data is updated, the
40 * non real time thread must call SwitchConfig() and redo the
41 * update on the other instance. SwitchConfig() blocks until it is
42 * safe to modify the other instance.
43 *
44 * The real time threads need one Reader object each to access the
45 * configuration data. This object must be created outside the
46 * real time thread. The Lock() function returns a reference to
47 * the data to be read, and Unlock() must be called when finished
48 * reading the data. (Neither Lock nor Unlock will block the real
49 * time thread, or use any system calls.)
50 *
51 * Note that the non real time side isn't safe for concurrent
52 * access, so if there are multiple non real time threads that
53 * update the configuration data, a mutex has to be used.
54 *
55 * Implementation note: the memory order parameters and fences are
56 * very carefully chosen to make the code fast but still safe for
57 * memory access reordering made by the CPU.
58 */
59 template<class T>
60 class SynchronizedConfig {
61 public:
62 SynchronizedConfig();
63
64 // methods for the real time thread
65
66 class Reader {
67 public:
68 /**
69 * Gets the configuration object for use by the
70 * real time thread. The object is safe to use
71 * (read only) until Unlock() is called.
72 *
73 * @returns a reference to the configuration
74 * object to be read by the real time
75 * thread
76 */
77 /*const*/ T& Lock() { //TODO const currently commented for the DoubleBuffer usage below
78 lock.store(lockCount += 2, memory_order_relaxed);
79 atomic_thread_fence(memory_order_seq_cst);
80 return parent->config[parent->indexAtomic.load(
81 memory_order_acquire)];
82 }
83
84 /**
85 * Unlock the configuration object. Unlock() must
86 * be called by the real time thread after it has
87 * finished reading the configuration object. If
88 * the non real time thread is waiting in
89 * SwitchConfig() it will be awaken when no real
90 * time threads are locked anymore.
91 */
92 void Unlock() {
93 lock.store(0, memory_order_release);
94 }
95
96 Reader(SynchronizedConfig& config);
97 Reader(SynchronizedConfig* config);
98 virtual ~Reader();
99 private:
100 friend class SynchronizedConfig;
101 SynchronizedConfig* parent;
102 int lockCount; // increased in every Lock(),
103 // lowest bit is always set.
104 atomic<int> lock; // equals lockCount when inside
105 // critical region, otherwise 0
106 Reader* next; // only used locally in SwitchConfig
107 int prevLock; // only used locally in SwitchConfig
108 };
109
110
111 // methods for the non real time thread
112
113 /**
114 * Gets the configuration object for use by the non real
115 * time thread. The object returned is not in use by the
116 * real time thread, so it can safely be updated. After
117 * the update is done, the non real time thread must call
118 * SwitchConfig() and the same update must be done again.
119 *
120 * @returns a reference to the configuration object to be
121 * updated by the non real time thread
122 */
123 T& GetConfigForUpdate();
124
125 /**
126 * Get the data on update side for read-only access.
127 */
128 const T& GetUnsafeUpdateConfig() const {
129 return config[updateIndex];
130 }
131
132 /**
133 * Atomically switch the newly updated configuration
134 * object with the one used by the real time thread, then
135 * wait for the real time thread to finish working with
136 * the old object before returning the old object.
137 * SwitchConfig() must be called by the non real time
138 * thread after an update has been done, and the object
139 * returned must be updated in the same way as the first.
140 *
141 * @returns a reference to the configuration object to be
142 * updated by the non real time thread
143 */
144 T& SwitchConfig();
145
146 private:
147 atomic<int> indexAtomic;
148 int updateIndex;
149 T config[2];
150 std::set<Reader*> readers;
151 };
152
153 template<class T> SynchronizedConfig<T>::SynchronizedConfig() :
154 indexAtomic(0) {
155 updateIndex = 1;
156 }
157
158 template<class T> T& SynchronizedConfig<T>::GetConfigForUpdate() {
159 return config[updateIndex];
160 }
161
162 template<class T> T& SynchronizedConfig<T>::SwitchConfig() {
163 indexAtomic.store(updateIndex, memory_order_release);
164 atomic_thread_fence(memory_order_seq_cst);
165
166 // first put all locking readers in a linked list
167 Reader* lockingReaders = 0;
168 for (typename std::set<Reader*>::iterator iter = readers.begin() ;
169 iter != readers.end() ;
170 iter++) {
171 (*iter)->prevLock = (*iter)->lock.load(memory_order_acquire);
172 if ((*iter)->prevLock) {
173 (*iter)->next = lockingReaders;
174 lockingReaders = *iter;
175 }
176 }
177
178 // wait until there are no locking readers left
179 while (lockingReaders) {
180 usleep(50000);
181 Reader** prev = &lockingReaders;
182 for (Reader* p = lockingReaders ; p ; p = p->next) {
183 if (p->lock.load(memory_order_acquire) == p->prevLock) {
184 prev = &p->next;
185 } else {
186 *prev = p->next; // unlink
187 }
188 }
189 }
190
191 updateIndex ^= 1;
192 return config[updateIndex];
193 }
194
195
196 // ----- Reader ----
197
198 template <class T>
199 SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig& config) :
200 parent(&config), lockCount(1), lock(0) {
201 parent->readers.insert(this);
202 }
203
204 template <class T>
205 SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig* config) :
206 parent(config), lockCount(1), lock(0) {
207 parent->readers.insert(this);
208 }
209
210 template <class T>
211 SynchronizedConfig<T>::Reader::~Reader() {
212 parent->readers.erase(this);
213 }
214
215
216 // ----- Convenience classes on top of SynchronizedConfig ----
217
218
219 /**
220 * Base interface class for classes that implement synchronization of data
221 * shared between multiple threads.
222 */
223 template<class T>
224 class Synchronizer {
225 public:
226 /**
227 * Signal intention to enter a synchronized code block. Depending
228 * on the actual implementation, this call may block the calling
229 * thread until it is safe to actually use the protected data. After
230 * this call returns, it is safe for the calling thread to access and
231 * modify the shared data. As soon as the thread is done with accessing
232 * the shared data, it MUST call endSync().
233 *
234 * @return the shared protected data
235 */
236 virtual void beginSync() = 0; //TODO: or call it lock() instead ?
237
238 /**
239 * Retrieve reference to critical, shared data. This method shall be
240 * called between a beginSync() and endSync() call pair, to be sure
241 * that shared data can be accessed safely.
242 */
243 virtual T& syncedData() = 0;
244
245 /**
246 * Signal that the synchronized code block has been left. Depending
247 * on the actual implementation, this call may block the calling
248 * thread for a certain amount of time.
249 */
250 virtual void endSync() = 0; //TODO: or call it unlock() instead ?
251 };
252
253 /**
254 * Wraps as a kind of pointer class some data object shared with other
255 * threads, to protect / synchronize the shared data against
256 * undeterministic concurrent access. It does so by locking the shared
257 * data in the Sync constructor and unlocking the shared data in the Sync
258 * destructor. Accordingly it can always be considered safe to access the
259 * shared data during the whole life time of the Sync object. Due to
260 * this design, a Sync object MUST only be accessed and destroyed
261 * by exactly one and the same thread which created that same Sync object.
262 */
263 template<class T>
264 class Sync {
265 public:
266 Sync(Synchronizer<T>* syncer) {
267 this->syncer = syncer;
268 syncer->beginSync();
269 }
270
271 virtual ~Sync() {
272 syncer->endSync();
273 }
274
275 /*Sync& operator =(const Sync& arg) {
276 *this->data = *arg.data;
277 return *this;
278 }*/
279
280 /*Sync& operator =(const T& arg) {
281 *this->data = arg;
282 return *this;
283 }*/
284
285 const T& operator *() const { return syncer->syncedData(); }
286 T& operator *() { return syncer->syncedData(); }
287
288 const T* operator ->() const { return &syncer->syncedData(); }
289 T* operator ->() { return &syncer->syncedData(); }
290
291 private:
292 Synchronizer<T>* syncer; ///< Points to the object that shall be responsible to protect the shared data.
293 };
294
295 /**
296 * BackBuffer object to be accessed by multiple non-real-time threads.
297 *
298 * Since a back buffer is designed for being accessed by non-real-time
299 * threads, its methods involved may block the calling thread for a long
300 * amount of time.
301 */
302 template<class T>
303 class BackBuffer : public SynchronizedConfig<T>, public Synchronizer<T> {
304 public:
305 virtual void beginSync() OVERRIDE {
306 mutex.Lock();
307 }
308
309 virtual T& syncedData() OVERRIDE {
310 return SynchronizedConfig<T>::GetConfigForUpdate();
311 }
312
313 virtual void endSync() OVERRIDE {
314 const T clone = SynchronizedConfig<T>::GetConfigForUpdate();
315 SynchronizedConfig<T>::SwitchConfig() = clone;
316 mutex.Unlock();
317 }
318
319 const T& unsafeData() const {
320 return SynchronizedConfig<T>::GetUnsafeUpdateConfig();
321 }
322
323 private:
324 Mutex mutex;
325 };
326
327 /**
328 * FrontBuffer object to be accessed by exactly ONE real-time thread.
329 * A front buffer is designed for real-time access. That is, its methods
330 * involved are lock free, that is none of them block the calling thread
331 * for a long time.
332 *
333 * If you need the front buffer's data to be accessed by multiple real-time
334 * threads instead, then you need to create multiple instances of the
335 * FrontBuffer object. They would point to the same data, but ensure
336 * protection against concurrent access among those real-time threads.
337 */
338 template<class T>
339 class FrontBuffer : public SynchronizedConfig<T>::Reader, public Synchronizer<T> {
340 public:
341 FrontBuffer(BackBuffer<T>& backBuffer) : SynchronizedConfig<T>::Reader::Reader(&backBuffer) {}
342 virtual void beginSync() OVERRIDE { data = &SynchronizedConfig<T>::Reader::Lock(); }
343 virtual T& syncedData() OVERRIDE { return *data; }
344 virtual void endSync() OVERRIDE { SynchronizedConfig<T>::Reader::Unlock(); }
345 private:
346 T* data;
347 };
348
349 /**
350 * Synchronization / protection of data shared between multiple threads by
351 * using a double buffer design. The FrontBuffer is meant to be accessed by
352 * exactly one real-time thread, whereas the BackBuffer is meant to be
353 * accessed by multiple non-real-time threads.
354 *
355 * This class is built on top of SynchronizedConfig as convenient API to
356 * reduce the amount of code required to protect shared data.
357 */
358 template<class T>
359 class DoubleBuffer {
360 public:
361 DoubleBuffer() : m_front(m_back) {}
362
363 /**
364 * Synchronized access of the shared data for EXACTLY one real-time
365 * thread.
366 *
367 * The returned shared data is wrapped into a Sync object, which
368 * ensures that the shared data is protected against concurrent access
369 * during the life time of the returned Sync object.
370 */
371 inline
372 Sync<T> front() { return Sync<T>(&m_front); }
373
374 /**
375 * Synchronized access of the shared data for multiple non-real-time
376 * threads.
377 *
378 * The returned shared data is wrapped into a Sync object, which
379 * ensures that the shared data is protected against concurrent access
380 * during the life time of the returned Sync object.
381 *
382 * As soon as the returned Sync object is destroyed, the FrontBuffer
383 * will automatically be exchanged by the hereby modified BackBuffer.
384 */
385 inline
386 Sync<T> back() { return Sync<T>(&m_back); }
387
388 /**
389 * Get the backbuffer data <b>unprotected</b>, that is <b>without</b>
390 * locking or any means of synchronizations.
391 *
392 * Due to its nature this must only be called for read access and
393 * you have to make sure by yourself, that the data/member you
394 * access is really safe for concurrent read access (i.e. SGI's
395 * implementation of std::vector::size() would be safe).
396 *
397 * Only use this when you are absolutely sure what you are doing!
398 */
399 const T& unsafeBack() const { return m_back.unsafeData(); }
400
401 private:
402 BackBuffer<T> m_back; ///< Back buffer (non real-time thread(s) side).
403 FrontBuffer<T> m_front; ///< Front buffer (real-time thread side).
404 };
405
406 } // namespace LinuxSampler
407
408 #endif

  ViewVC Help
Powered by ViewVC