/[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 3034 - (hide annotations) (download) (as text)
Mon Oct 31 00:05:00 2016 UTC (7 years, 5 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 persson 840 /***************************************************************************
2     * *
3 schoenebeck 3034 * Copyright (C) 2006-2016 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 2506 * Get the data on update side for read-only access.
127 schoenebeck 2500 */
128     const T& GetUnsafeUpdateConfig() const {
129     return config[updateIndex];
130     }
131    
132     /**
133 persson 840 * 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 persson 1790 atomic<int> indexAtomic;
148 persson 840 int updateIndex;
149     T config[2];
150 persson 846 std::set<Reader*> readers;
151 persson 840 };
152    
153 persson 1790 template<class T> SynchronizedConfig<T>::SynchronizedConfig() :
154     indexAtomic(0) {
155 persson 846 updateIndex = 1;
156 persson 840 }
157    
158     template<class T> T& SynchronizedConfig<T>::GetConfigForUpdate() {
159     return config[updateIndex];
160     }
161    
162     template<class T> T& SynchronizedConfig<T>::SwitchConfig() {
163 persson 1790 indexAtomic.store(updateIndex, memory_order_release);
164     atomic_thread_fence(memory_order_seq_cst);
165 persson 846
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 persson 1887 (*iter)->prevLock = (*iter)->lock.load(memory_order_acquire);
172     if ((*iter)->prevLock) {
173 persson 846 (*iter)->next = lockingReaders;
174     lockingReaders = *iter;
175     }
176     }
177    
178     // wait until there are no locking readers left
179     while (lockingReaders) {
180 persson 840 usleep(50000);
181 persson 846 Reader** prev = &lockingReaders;
182     for (Reader* p = lockingReaders ; p ; p = p->next) {
183 persson 1887 if (p->lock.load(memory_order_acquire) == p->prevLock) {
184     prev = &p->next;
185     } else {
186     *prev = p->next; // unlink
187     }
188 persson 846 }
189     }
190    
191     updateIndex ^= 1;
192     return config[updateIndex];
193 persson 840 }
194    
195 persson 846
196     // ----- Reader ----
197    
198     template <class T>
199 persson 1790 SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig& config) :
200 schoenebeck 3034 parent(&config), lockCount(1), lock(0) {
201 schoenebeck 2453 parent->readers.insert(this);
202     }
203    
204     template <class T>
205     SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig* config) :
206 schoenebeck 3034 parent(config), lockCount(1), lock(0) {
207 schoenebeck 2453 parent->readers.insert(this);
208 persson 846 }
209    
210     template <class T>
211     SynchronizedConfig<T>::Reader::~Reader() {
212 schoenebeck 2453 parent->readers.erase(this);
213 persson 846 }
214 schoenebeck 2453
215    
216     // ----- Convenience classes on top of SynchronizedConfig ----
217 persson 846
218 schoenebeck 2453
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 schoenebeck 2500 virtual void beginSync() = 0; //TODO: or call it lock() instead ?
237    
238 schoenebeck 2453 /**
239 schoenebeck 2500 * 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 schoenebeck 2453 * 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 schoenebeck 2500 syncer->beginSync();
269 schoenebeck 2453 }
270    
271     virtual ~Sync() {
272     syncer->endSync();
273     }
274    
275 schoenebeck 2500 /*Sync& operator =(const Sync& arg) {
276 schoenebeck 2453 *this->data = *arg.data;
277     return *this;
278 schoenebeck 2500 }*/
279 schoenebeck 2453
280 schoenebeck 2500 /*Sync& operator =(const T& arg) {
281 schoenebeck 2453 *this->data = arg;
282     return *this;
283 schoenebeck 2500 }*/
284 schoenebeck 2453
285 schoenebeck 2500 const T& operator *() const { return syncer->syncedData(); }
286     T& operator *() { return syncer->syncedData(); }
287 schoenebeck 2453
288 schoenebeck 2500 const T* operator ->() const { return &syncer->syncedData(); }
289     T* operator ->() { return &syncer->syncedData(); }
290 schoenebeck 2453
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 schoenebeck 2500 virtual void beginSync() OVERRIDE {
306 schoenebeck 2453 mutex.Lock();
307     }
308 schoenebeck 2500
309     virtual T& syncedData() OVERRIDE {
310     return SynchronizedConfig<T>::GetConfigForUpdate();
311     }
312 schoenebeck 2453
313     virtual void endSync() OVERRIDE {
314 schoenebeck 2500 const T clone = SynchronizedConfig<T>::GetConfigForUpdate();
315 schoenebeck 2453 SynchronizedConfig<T>::SwitchConfig() = clone;
316     mutex.Unlock();
317     }
318    
319 schoenebeck 2500 const T& unsafeData() const {
320     return SynchronizedConfig<T>::GetUnsafeUpdateConfig();
321     }
322    
323 schoenebeck 2453 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 schoenebeck 2500 virtual void beginSync() OVERRIDE { data = &SynchronizedConfig<T>::Reader::Lock(); }
343     virtual T& syncedData() OVERRIDE { return *data; }
344 schoenebeck 2453 virtual void endSync() OVERRIDE { SynchronizedConfig<T>::Reader::Unlock(); }
345 schoenebeck 2500 private:
346     T* data;
347 schoenebeck 2453 };
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 schoenebeck 2500 /**
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 schoenebeck 2453 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 persson 840 } // namespace LinuxSampler
407    
408     #endif

  ViewVC Help
Powered by ViewVC