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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1887 by persson, Sat Apr 18 08:17:16 2009 UTC revision 2500 by schoenebeck, Fri Jan 10 12:20:05 2014 UTC
# Line 1  Line 1 
1  /***************************************************************************  /***************************************************************************
2   *                                                                         *   *                                                                         *
3   *   Copyright (C) 2006-2009 Andreas Persson                               *   *   Copyright (C) 2006-2014 Andreas Persson                               *
4   *                                                                         *   *                                                                         *
5   *   This program is free software; you can redistribute it and/or modify  *   *   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  *   *   it under the terms of the GNU General Public License as published by  *
# Line 24  Line 24 
24  #include <set>  #include <set>
25  #include <unistd.h>  #include <unistd.h>
26  #include "lsatomic.h"  #include "lsatomic.h"
27    #include "Mutex.h"
28    
29  namespace LinuxSampler {  namespace LinuxSampler {
30    
31      /**      /**
32       * Thread safe management of configuration data, where the data is       * Thread-safe management of configuration data, where the data is
33       * updated by a single non real time thread and read by a number       * updated by a single non real time thread and read by a number
34       * of real time threads.       * of real time threads.
35       *       *
# Line 46  namespace LinuxSampler { Line 47  namespace LinuxSampler {
47       * the data to be read, and Unlock() must be called when finished       * the data to be read, and Unlock() must be called when finished
48       * reading the data. (Neither Lock nor Unlock will block the real       * reading the data. (Neither Lock nor Unlock will block the real
49       * time thread, or use any system calls.)       * 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>      template<class T>
60      class SynchronizedConfig {      class SynchronizedConfig {
# Line 65  namespace LinuxSampler { Line 74  namespace LinuxSampler {
74                       *          object to be read by the real time                       *          object to be read by the real time
75                       *          thread                       *          thread
76                       */                       */
77                      const T& Lock() {                      /*const*/ T& Lock() { //TODO const currently commented for the DoubleBuffer usage below
78                          lock.store(lockCount += 2, memory_order_relaxed);                          lock.store(lockCount += 2, memory_order_relaxed);
79                          atomic_thread_fence(memory_order_seq_cst);                          atomic_thread_fence(memory_order_seq_cst);
80                          return parent.config[parent.indexAtomic.load(                          return parent->config[parent->indexAtomic.load(
81                                  memory_order_acquire)];                                  memory_order_acquire)];
82                      }                      }
83    
# Line 85  namespace LinuxSampler { Line 94  namespace LinuxSampler {
94                      }                      }
95    
96                      Reader(SynchronizedConfig& config);                      Reader(SynchronizedConfig& config);
97                      ~Reader();                      Reader(SynchronizedConfig* config);
98                        virtual ~Reader();
99                  private:                  private:
100                      friend class SynchronizedConfig;                      friend class SynchronizedConfig;
101                      SynchronizedConfig& parent;                      SynchronizedConfig* parent;
102                      int lockCount; // increased in every Lock(),                      int lockCount; // increased in every Lock(),
103                                     // lowest bit is always set.                                     // lowest bit is always set.
104                      atomic<int> lock; // equals lockCount when inside                      atomic<int> lock; // equals lockCount when inside
# Line 113  namespace LinuxSampler { Line 123  namespace LinuxSampler {
123              T& GetConfigForUpdate();              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               * Atomically switch the newly updated configuration
142               * object with the one used by the real time thread, then               * object with the one used by the real time thread, then
143               * wait for the real time thread to finish working with               * wait for the real time thread to finish working with
# Line 180  namespace LinuxSampler { Line 205  namespace LinuxSampler {
205    
206      template <class T>      template <class T>
207      SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig& config) :      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) {          parent(config), lock(0), lockCount(1) {
215          parent.readers.insert(this);          parent->readers.insert(this);
216      }      }
217    
218      template <class T>      template <class T>
219      SynchronizedConfig<T>::Reader::~Reader() {      SynchronizedConfig<T>::Reader::~Reader() {
220          parent.readers.erase(this);          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  } // namespace LinuxSampler
415    

Legend:
Removed from v.1887  
changed lines
  Added in v.2500

  ViewVC Help
Powered by ViewVC