/[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 2506 by schoenebeck, Sun Jan 12 11:27: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 for read-only access.
127                 */
128                const T& GetUnsafeUpdateConfig() const {
129                    return config[updateIndex];
130                }
131    
132                /**
133               * Atomically switch the newly updated configuration               * Atomically switch the newly updated configuration
134               * object with the one used by the real time thread, then               * object with the one used by the real time thread, then
135               * 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 197  namespace LinuxSampler {
197    
198      template <class T>      template <class T>
199      SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig& config) :      SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig& config) :
200            parent(&config), lock(0), lockCount(1) {
201            parent->readers.insert(this);
202        }
203        
204        template <class T>
205        SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig* config) :
206          parent(config), lock(0), lockCount(1) {          parent(config), lock(0), lockCount(1) {
207          parent.readers.insert(this);          parent->readers.insert(this);
208      }      }
209    
210      template <class T>      template <class T>
211      SynchronizedConfig<T>::Reader::~Reader() {      SynchronizedConfig<T>::Reader::~Reader() {
212          parent.readers.erase(this);          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  } // namespace LinuxSampler
407    

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

  ViewVC Help
Powered by ViewVC