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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2500 - (hide 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 persson 840 /***************************************************************************
2     * *
3 schoenebeck 2500 * Copyright (C) 2006-2014 Andreas Persson *
4 persson 840 * *
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 persson 1790 #ifndef SYNCHRONIZEDCONFIG_H
22     #define SYNCHRONIZEDCONFIG_H
23 persson 840
24 persson 846 #include <set>
25 persson 1790 #include <unistd.h>
26     #include "lsatomic.h"
27 schoenebeck 2453 #include "Mutex.h"
28 persson 840
29     namespace LinuxSampler {
30    
31     /**
32 persson 2343 * Thread-safe management of configuration data, where the data is
33 persson 846 * updated by a single non real time thread and read by a number
34     * of real time threads.
35 persson 840 *
36     * The synchronization is achieved by using two instances of the
37     * configuration data. The non real time thread gets access to the
38 persson 846 * instance not currently in use by the real time threads by
39 persson 840 * 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 persson 846 * The real time threads need one Reader object each to access the
45 persson 1038 * 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 persson 846 * reading the data. (Neither Lock nor Unlock will block the real
49     * time thread, or use any system calls.)
50 persson 2343 *
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 persson 840 */
59     template<class T>
60     class SynchronizedConfig {
61     public:
62     SynchronizedConfig();
63    
64     // methods for the real time thread
65    
66 persson 846 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 schoenebeck 2453 /*const*/ T& Lock() { //TODO const currently commented for the DoubleBuffer usage below
78 persson 1887 lock.store(lockCount += 2, memory_order_relaxed);
79 persson 1790 atomic_thread_fence(memory_order_seq_cst);
80 schoenebeck 2453 return parent->config[parent->indexAtomic.load(
81 persson 1790 memory_order_acquire)];
82 persson 846 }
83 persson 840
84 persson 846 /**
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 persson 1887 lock.store(0, memory_order_release);
94 persson 846 }
95 persson 840
96 persson 846 Reader(SynchronizedConfig& config);
97 schoenebeck 2453 Reader(SynchronizedConfig* config);
98     virtual ~Reader();
99 persson 846 private:
100     friend class SynchronizedConfig;
101 schoenebeck 2453 SynchronizedConfig* parent;
102 persson 1887 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 persson 846 };
109 persson 840
110 persson 846
111 persson 840 // 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 schoenebeck 2500 * 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 persson 840 * 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 persson 1790 atomic<int> indexAtomic;
156 persson 840 int updateIndex;
157     T config[2];
158 persson 846 std::set<Reader*> readers;
159 persson 840 };
160    
161 persson 1790 template<class T> SynchronizedConfig<T>::SynchronizedConfig() :
162     indexAtomic(0) {
163 persson 846 updateIndex = 1;
164 persson 840 }
165    
166     template<class T> T& SynchronizedConfig<T>::GetConfigForUpdate() {
167     return config[updateIndex];
168     }
169    
170     template<class T> T& SynchronizedConfig<T>::SwitchConfig() {
171 persson 1790 indexAtomic.store(updateIndex, memory_order_release);
172     atomic_thread_fence(memory_order_seq_cst);
173 persson 846
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 persson 1887 (*iter)->prevLock = (*iter)->lock.load(memory_order_acquire);
180     if ((*iter)->prevLock) {
181 persson 846 (*iter)->next = lockingReaders;
182     lockingReaders = *iter;
183     }
184     }
185    
186     // wait until there are no locking readers left
187     while (lockingReaders) {
188 persson 840 usleep(50000);
189 persson 846 Reader** prev = &lockingReaders;
190     for (Reader* p = lockingReaders ; p ; p = p->next) {
191 persson 1887 if (p->lock.load(memory_order_acquire) == p->prevLock) {
192     prev = &p->next;
193     } else {
194     *prev = p->next; // unlink
195     }
196 persson 846 }
197     }
198    
199     updateIndex ^= 1;
200     return config[updateIndex];
201 persson 840 }
202    
203 persson 846
204     // ----- Reader ----
205    
206     template <class T>
207 persson 1790 SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig& config) :
208 schoenebeck 2453 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 persson 1887 parent(config), lock(0), lockCount(1) {
215 schoenebeck 2453 parent->readers.insert(this);
216 persson 846 }
217    
218     template <class T>
219     SynchronizedConfig<T>::Reader::~Reader() {
220 schoenebeck 2453 parent->readers.erase(this);
221 persson 846 }
222 schoenebeck 2453
223    
224     // ----- Convenience classes on top of SynchronizedConfig ----
225 persson 846
226 schoenebeck 2453
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 schoenebeck 2500 virtual void beginSync() = 0; //TODO: or call it lock() instead ?
245    
246 schoenebeck 2453 /**
247 schoenebeck 2500 * 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 schoenebeck 2453 * 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 schoenebeck 2500 syncer->beginSync();
277 schoenebeck 2453 }
278    
279     virtual ~Sync() {
280     syncer->endSync();
281     }
282    
283 schoenebeck 2500 /*Sync& operator =(const Sync& arg) {
284 schoenebeck 2453 *this->data = *arg.data;
285     return *this;
286 schoenebeck 2500 }*/
287 schoenebeck 2453
288 schoenebeck 2500 /*Sync& operator =(const T& arg) {
289 schoenebeck 2453 *this->data = arg;
290     return *this;
291 schoenebeck 2500 }*/
292 schoenebeck 2453
293 schoenebeck 2500 const T& operator *() const { return syncer->syncedData(); }
294     T& operator *() { return syncer->syncedData(); }
295 schoenebeck 2453
296 schoenebeck 2500 const T* operator ->() const { return &syncer->syncedData(); }
297     T* operator ->() { return &syncer->syncedData(); }
298 schoenebeck 2453
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 schoenebeck 2500 virtual void beginSync() OVERRIDE {
314 schoenebeck 2453 mutex.Lock();
315     }
316 schoenebeck 2500
317     virtual T& syncedData() OVERRIDE {
318     return SynchronizedConfig<T>::GetConfigForUpdate();
319     }
320 schoenebeck 2453
321     virtual void endSync() OVERRIDE {
322 schoenebeck 2500 const T clone = SynchronizedConfig<T>::GetConfigForUpdate();
323 schoenebeck 2453 SynchronizedConfig<T>::SwitchConfig() = clone;
324     mutex.Unlock();
325     }
326    
327 schoenebeck 2500 const T& unsafeData() const {
328     return SynchronizedConfig<T>::GetUnsafeUpdateConfig();
329     }
330    
331 schoenebeck 2453 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 schoenebeck 2500 virtual void beginSync() OVERRIDE { data = &SynchronizedConfig<T>::Reader::Lock(); }
351     virtual T& syncedData() OVERRIDE { return *data; }
352 schoenebeck 2453 virtual void endSync() OVERRIDE { SynchronizedConfig<T>::Reader::Unlock(); }
353 schoenebeck 2500 private:
354     T* data;
355 schoenebeck 2453 };
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 schoenebeck 2500 /**
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 schoenebeck 2453 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 persson 840 } // namespace LinuxSampler
415    
416     #endif

  ViewVC Help
Powered by ViewVC