/[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 2343 - (show annotations) (download) (as text)
Sun Apr 29 16:14:45 2012 UTC (11 years, 11 months ago) by persson
File MIME type: text/x-c++hdr
File size: 8427 byte(s)
* fixed configure script error with old autoconf versions
* LV2: use the new lv2 package if present
* lsatomic.h: use gcc provided atomic functions if building with gcc
  4.7 and C++11
* added comments in lsatomic.h

1 /***************************************************************************
2 * *
3 * Copyright (C) 2006-2012 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
28 namespace LinuxSampler {
29
30 /**
31 * Thread-safe management of configuration data, where the data is
32 * updated by a single non real time thread and read by a number
33 * of real time threads.
34 *
35 * The synchronization is achieved by using two instances of the
36 * configuration data. The non real time thread gets access to the
37 * instance not currently in use by the real time threads by
38 * calling GetConfigForUpdate(). After the data is updated, the
39 * non real time thread must call SwitchConfig() and redo the
40 * update on the other instance. SwitchConfig() blocks until it is
41 * safe to modify the other instance.
42 *
43 * The real time threads need one Reader object each to access the
44 * configuration data. This object must be created outside the
45 * real time thread. The Lock() function returns a reference to
46 * the data to be read, and Unlock() must be called when finished
47 * reading the data. (Neither Lock nor Unlock will block the real
48 * time thread, or use any system calls.)
49 *
50 * Note that the non real time side isn't safe for concurrent
51 * access, so if there are multiple non real time threads that
52 * update the configuration data, a mutex has to be used.
53 *
54 * Implementation note: the memory order parameters and fences are
55 * very carefully chosen to make the code fast but still safe for
56 * memory access reordering made by the CPU.
57 */
58 template<class T>
59 class SynchronizedConfig {
60 public:
61 SynchronizedConfig();
62
63 // methods for the real time thread
64
65 class Reader {
66 public:
67 /**
68 * Gets the configuration object for use by the
69 * real time thread. The object is safe to use
70 * (read only) until Unlock() is called.
71 *
72 * @returns a reference to the configuration
73 * object to be read by the real time
74 * thread
75 */
76 const T& Lock() {
77 lock.store(lockCount += 2, memory_order_relaxed);
78 atomic_thread_fence(memory_order_seq_cst);
79 return parent.config[parent.indexAtomic.load(
80 memory_order_acquire)];
81 }
82
83 /**
84 * Unlock the configuration object. Unlock() must
85 * be called by the real time thread after it has
86 * finished reading the configuration object. If
87 * the non real time thread is waiting in
88 * SwitchConfig() it will be awaken when no real
89 * time threads are locked anymore.
90 */
91 void Unlock() {
92 lock.store(0, memory_order_release);
93 }
94
95 Reader(SynchronizedConfig& config);
96 ~Reader();
97 private:
98 friend class SynchronizedConfig;
99 SynchronizedConfig& parent;
100 int lockCount; // increased in every Lock(),
101 // lowest bit is always set.
102 atomic<int> lock; // equals lockCount when inside
103 // critical region, otherwise 0
104 Reader* next; // only used locally in SwitchConfig
105 int prevLock; // only used locally in SwitchConfig
106 };
107
108
109 // methods for the non real time thread
110
111 /**
112 * Gets the configuration object for use by the non real
113 * time thread. The object returned is not in use by the
114 * real time thread, so it can safely be updated. After
115 * the update is done, the non real time thread must call
116 * SwitchConfig() and the same update must be done again.
117 *
118 * @returns a reference to the configuration object to be
119 * updated by the non real time thread
120 */
121 T& GetConfigForUpdate();
122
123 /**
124 * Atomically switch the newly updated configuration
125 * object with the one used by the real time thread, then
126 * wait for the real time thread to finish working with
127 * the old object before returning the old object.
128 * SwitchConfig() must be called by the non real time
129 * thread after an update has been done, and the object
130 * returned must be updated in the same way as the first.
131 *
132 * @returns a reference to the configuration object to be
133 * updated by the non real time thread
134 */
135 T& SwitchConfig();
136
137 private:
138 atomic<int> indexAtomic;
139 int updateIndex;
140 T config[2];
141 std::set<Reader*> readers;
142 };
143
144 template<class T> SynchronizedConfig<T>::SynchronizedConfig() :
145 indexAtomic(0) {
146 updateIndex = 1;
147 }
148
149 template<class T> T& SynchronizedConfig<T>::GetConfigForUpdate() {
150 return config[updateIndex];
151 }
152
153 template<class T> T& SynchronizedConfig<T>::SwitchConfig() {
154 indexAtomic.store(updateIndex, memory_order_release);
155 atomic_thread_fence(memory_order_seq_cst);
156
157 // first put all locking readers in a linked list
158 Reader* lockingReaders = 0;
159 for (typename std::set<Reader*>::iterator iter = readers.begin() ;
160 iter != readers.end() ;
161 iter++) {
162 (*iter)->prevLock = (*iter)->lock.load(memory_order_acquire);
163 if ((*iter)->prevLock) {
164 (*iter)->next = lockingReaders;
165 lockingReaders = *iter;
166 }
167 }
168
169 // wait until there are no locking readers left
170 while (lockingReaders) {
171 usleep(50000);
172 Reader** prev = &lockingReaders;
173 for (Reader* p = lockingReaders ; p ; p = p->next) {
174 if (p->lock.load(memory_order_acquire) == p->prevLock) {
175 prev = &p->next;
176 } else {
177 *prev = p->next; // unlink
178 }
179 }
180 }
181
182 updateIndex ^= 1;
183 return config[updateIndex];
184 }
185
186
187 // ----- Reader ----
188
189 template <class T>
190 SynchronizedConfig<T>::Reader::Reader(SynchronizedConfig& config) :
191 parent(config), lock(0), lockCount(1) {
192 parent.readers.insert(this);
193 }
194
195 template <class T>
196 SynchronizedConfig<T>::Reader::~Reader() {
197 parent.readers.erase(this);
198 }
199
200 } // namespace LinuxSampler
201
202 #endif

  ViewVC Help
Powered by ViewVC