/[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 2500 - (show annotations) (download) (as text)
Fri Jan 10 12:20:05 2014 UTC (10 years, 3 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 16802 byte(s)
* Added support for multiple MIDI input ports per sampler channel (added
  various new C++ API methods for this new feature/design, old C++ API
  methods are now marked as deprecated but should still provide full
  behavior backward compatibility).
* LSCP Network interface: Added the following new LSCP commands for the new
  feature mentioned above: "ADD CHANNEL MIDI_INPUT",
  "REMOVE CHANNEL MIDI_INPUT" and "LIST CHANNEL MIDI_INPUTS". As with the
  C++ API changes, the old LSCP commands for MIDI input management are now
  marked as deprecated, but are still there and should provide full behavior
  backward compatibility.
* New LSCP specification document (LSCP v1.6).
* AbstractEngine::GSCheckSum(): don't allocate memory on the stack (was
  unsafe and caused compilation error with old clang 2.x).
* Bumped version (1.0.0.svn25).

1 /***************************************************************************
2 * *
3 * Copyright (C) 2006-2014 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 <b>unprotected</b>, that is
127 * <b>without</b> locking or any means of synchronizations.
128 *
129 * Due to its nature this must only be called for read access and
130 * you have to make sure by yourself, that the data/member you
131 * access is really safe for concurrent read access (i.e. SGI's
132 * implementation of std::vector::size() would be safe).
133 *
134 * Only use this when you are absolutely sure what you are doing!
135 */
136 const T& GetUnsafeUpdateConfig() const {
137 return config[updateIndex];
138 }
139
140 /**
141 * Atomically switch the newly updated configuration
142 * object with the one used by the real time thread, then
143 * wait for the real time thread to finish working with
144 * the old object before returning the old object.
145 * SwitchConfig() must be called by the non real time
146 * thread after an update has been done, and the object
147 * returned must be updated in the same way as the first.
148 *
149 * @returns a reference to the configuration object to be
150 * updated by the non real time thread
151 */
152 T& SwitchConfig();
153
154 private:
155 atomic<int> indexAtomic;
156 int updateIndex;
157 T config[2];
158 std::set<Reader*> readers;
159 };
160
161 template<class T> SynchronizedConfig<T>::SynchronizedConfig() :
162 indexAtomic(0) {
163 updateIndex = 1;
164 }
165
166 template<class T> T& SynchronizedConfig<T>::GetConfigForUpdate() {
167 return config[updateIndex];
168 }
169
170 template<class T> T& SynchronizedConfig<T>::SwitchConfig() {
171 indexAtomic.store(updateIndex, memory_order_release);
172 atomic_thread_fence(memory_order_seq_cst);
173
174 // first put all locking readers in a linked list
175 Reader* lockingReaders = 0;
176 for (typename std::set<Reader*>::iterator iter = readers.begin() ;
177 iter != readers.end() ;
178 iter++) {
179 (*iter)->prevLock = (*iter)->lock.load(memory_order_acquire);
180 if ((*iter)->prevLock) {
181 (*iter)->next = lockingReaders;
182 lockingReaders = *iter;
183 }
184 }
185
186 // wait until there are no locking readers left
187 while (lockingReaders) {
188 usleep(50000);
189 Reader** prev = &lockingReaders;
190 for (Reader* p = lockingReaders ; p ; p = p->next) {
191 if (p->lock.load(memory_order_acquire) == p->prevLock) {
192 prev = &p->next;
193 } else {
194 *prev = p->next; // unlink
195 }
196 }
197 }
198
199 updateIndex ^= 1;
200 return config[updateIndex];
201 }
202
203
204 // ----- Reader ----
205
206 template <class T>
207 SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig& config) :
208 parent(&config), lock(0), lockCount(1) {
209 parent->readers.insert(this);
210 }
211
212 template <class T>
213 SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig* config) :
214 parent(config), lock(0), lockCount(1) {
215 parent->readers.insert(this);
216 }
217
218 template <class T>
219 SynchronizedConfig<T>::Reader::~Reader() {
220 parent->readers.erase(this);
221 }
222
223
224 // ----- Convenience classes on top of SynchronizedConfig ----
225
226
227 /**
228 * Base interface class for classes that implement synchronization of data
229 * shared between multiple threads.
230 */
231 template<class T>
232 class Synchronizer {
233 public:
234 /**
235 * Signal intention to enter a synchronized code block. Depending
236 * on the actual implementation, this call may block the calling
237 * thread until it is safe to actually use the protected data. After
238 * this call returns, it is safe for the calling thread to access and
239 * modify the shared data. As soon as the thread is done with accessing
240 * the shared data, it MUST call endSync().
241 *
242 * @return the shared protected data
243 */
244 virtual void beginSync() = 0; //TODO: or call it lock() instead ?
245
246 /**
247 * Retrieve reference to critical, shared data. This method shall be
248 * called between a beginSync() and endSync() call pair, to be sure
249 * that shared data can be accessed safely.
250 */
251 virtual T& syncedData() = 0;
252
253 /**
254 * Signal that the synchronized code block has been left. Depending
255 * on the actual implementation, this call may block the calling
256 * thread for a certain amount of time.
257 */
258 virtual void endSync() = 0; //TODO: or call it unlock() instead ?
259 };
260
261 /**
262 * Wraps as a kind of pointer class some data object shared with other
263 * threads, to protect / synchronize the shared data against
264 * undeterministic concurrent access. It does so by locking the shared
265 * data in the Sync constructor and unlocking the shared data in the Sync
266 * destructor. Accordingly it can always be considered safe to access the
267 * shared data during the whole life time of the Sync object. Due to
268 * this design, a Sync object MUST only be accessed and destroyed
269 * by exactly one and the same thread which created that same Sync object.
270 */
271 template<class T>
272 class Sync {
273 public:
274 Sync(Synchronizer<T>* syncer) {
275 this->syncer = syncer;
276 syncer->beginSync();
277 }
278
279 virtual ~Sync() {
280 syncer->endSync();
281 }
282
283 /*Sync& operator =(const Sync& arg) {
284 *this->data = *arg.data;
285 return *this;
286 }*/
287
288 /*Sync& operator =(const T& arg) {
289 *this->data = arg;
290 return *this;
291 }*/
292
293 const T& operator *() const { return syncer->syncedData(); }
294 T& operator *() { return syncer->syncedData(); }
295
296 const T* operator ->() const { return &syncer->syncedData(); }
297 T* operator ->() { return &syncer->syncedData(); }
298
299 private:
300 Synchronizer<T>* syncer; ///< Points to the object that shall be responsible to protect the shared data.
301 };
302
303 /**
304 * BackBuffer object to be accessed by multiple non-real-time threads.
305 *
306 * Since a back buffer is designed for being accessed by non-real-time
307 * threads, its methods involved may block the calling thread for a long
308 * amount of time.
309 */
310 template<class T>
311 class BackBuffer : public SynchronizedConfig<T>, public Synchronizer<T> {
312 public:
313 virtual void beginSync() OVERRIDE {
314 mutex.Lock();
315 }
316
317 virtual T& syncedData() OVERRIDE {
318 return SynchronizedConfig<T>::GetConfigForUpdate();
319 }
320
321 virtual void endSync() OVERRIDE {
322 const T clone = SynchronizedConfig<T>::GetConfigForUpdate();
323 SynchronizedConfig<T>::SwitchConfig() = clone;
324 mutex.Unlock();
325 }
326
327 const T& unsafeData() const {
328 return SynchronizedConfig<T>::GetUnsafeUpdateConfig();
329 }
330
331 private:
332 Mutex mutex;
333 };
334
335 /**
336 * FrontBuffer object to be accessed by exactly ONE real-time thread.
337 * A front buffer is designed for real-time access. That is, its methods
338 * involved are lock free, that is none of them block the calling thread
339 * for a long time.
340 *
341 * If you need the front buffer's data to be accessed by multiple real-time
342 * threads instead, then you need to create multiple instances of the
343 * FrontBuffer object. They would point to the same data, but ensure
344 * protection against concurrent access among those real-time threads.
345 */
346 template<class T>
347 class FrontBuffer : public SynchronizedConfig<T>::Reader, public Synchronizer<T> {
348 public:
349 FrontBuffer(BackBuffer<T>& backBuffer) : SynchronizedConfig<T>::Reader::Reader(&backBuffer) {}
350 virtual void beginSync() OVERRIDE { data = &SynchronizedConfig<T>::Reader::Lock(); }
351 virtual T& syncedData() OVERRIDE { return *data; }
352 virtual void endSync() OVERRIDE { SynchronizedConfig<T>::Reader::Unlock(); }
353 private:
354 T* data;
355 };
356
357 /**
358 * Synchronization / protection of data shared between multiple threads by
359 * using a double buffer design. The FrontBuffer is meant to be accessed by
360 * exactly one real-time thread, whereas the BackBuffer is meant to be
361 * accessed by multiple non-real-time threads.
362 *
363 * This class is built on top of SynchronizedConfig as convenient API to
364 * reduce the amount of code required to protect shared data.
365 */
366 template<class T>
367 class DoubleBuffer {
368 public:
369 DoubleBuffer() : m_front(m_back) {}
370
371 /**
372 * Synchronized access of the shared data for EXACTLY one real-time
373 * thread.
374 *
375 * The returned shared data is wrapped into a Sync object, which
376 * ensures that the shared data is protected against concurrent access
377 * during the life time of the returned Sync object.
378 */
379 inline
380 Sync<T> front() { return Sync<T>(&m_front); }
381
382 /**
383 * Synchronized access of the shared data for multiple non-real-time
384 * threads.
385 *
386 * The returned shared data is wrapped into a Sync object, which
387 * ensures that the shared data is protected against concurrent access
388 * during the life time of the returned Sync object.
389 *
390 * As soon as the returned Sync object is destroyed, the FrontBuffer
391 * will automatically be exchanged by the hereby modified BackBuffer.
392 */
393 inline
394 Sync<T> back() { return Sync<T>(&m_back); }
395
396 /**
397 * Get the backbuffer data <b>unprotected</b>, that is <b>without</b>
398 * locking or any means of synchronizations.
399 *
400 * Due to its nature this must only be called for read access and
401 * you have to make sure by yourself, that the data/member you
402 * access is really safe for concurrent read access (i.e. SGI's
403 * implementation of std::vector::size() would be safe).
404 *
405 * Only use this when you are absolutely sure what you are doing!
406 */
407 const T& unsafeBack() const { return m_back.unsafeData(); }
408
409 private:
410 BackBuffer<T> m_back; ///< Back buffer (non real-time thread(s) side).
411 FrontBuffer<T> m_front; ///< Front buffer (real-time thread side).
412 };
413
414 } // namespace LinuxSampler
415
416 #endif

  ViewVC Help
Powered by ViewVC