1 | |
2 | // |
3 | // This source file is part of appleseed. |
4 | // Visit http://appleseedhq.net/ for additional information and resources. |
5 | // |
6 | // This software is released under the MIT license. |
7 | // |
8 | // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited |
9 | // Copyright (c) 2014-2017 Francois Beaune, The appleseedhq Organization |
10 | // |
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
12 | // of this software and associated documentation files (the "Software"), to deal |
13 | // in the Software without restriction, including without limitation the rights |
14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
15 | // copies of the Software, and to permit persons to whom the Software is |
16 | // furnished to do so, subject to the following conditions: |
17 | // |
18 | // The above copyright notice and this permission notice shall be included in |
19 | // all copies or substantial portions of the Software. |
20 | // |
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
27 | // THE SOFTWARE. |
28 | // |
29 | |
30 | #ifndef APPLESEED_FOUNDATION_PLATFORM_THREAD_H |
31 | #define APPLESEED_FOUNDATION_PLATFORM_THREAD_H |
32 | |
33 | // appleseed.foundation headers. |
34 | #include "foundation/core/concepts/noncopyable.h" |
35 | #include "foundation/platform/atomic.h" |
36 | #include "foundation/platform/types.h" |
37 | |
38 | // appleseed.main headers. |
39 | #include "main/dllsymbol.h" |
40 | |
41 | // Boost headers. |
42 | #include "boost/smart_ptr/detail/spinlock.hpp" |
43 | #include "boost/thread/locks.hpp" |
44 | #include "boost/thread/mutex.hpp" |
45 | #include "boost/thread/thread.hpp" |
46 | |
47 | // Forward declarations. |
48 | namespace foundation { class IAbortSwitch; } |
49 | namespace foundation { class Logger; } |
50 | |
51 | namespace foundation |
52 | { |
53 | |
54 | // |
55 | // Utility functions. |
56 | // |
57 | |
58 | // Set the name of the current thread. |
59 | // For portability, limit the name to 16 characters, including the terminating zero. |
60 | APPLESEED_DLLSYMBOL void set_current_thread_name(const char* name); |
61 | |
62 | // Suspend the current thread for a given number of milliseconds. |
63 | APPLESEED_DLLSYMBOL void sleep(const uint32 ms); |
64 | APPLESEED_DLLSYMBOL void sleep(const uint32 ms, IAbortSwitch& abort_switch); |
65 | |
66 | // Give up the remainder of the current thread's time slice, to allow other threads to run. |
67 | APPLESEED_DLLSYMBOL void yield(); |
68 | |
69 | |
70 | // |
71 | // A simple spinlock. |
72 | // |
73 | |
74 | class Spinlock |
75 | : public NonCopyable |
76 | { |
77 | public: |
78 | Spinlock(); |
79 | |
80 | bool try_lock(); |
81 | void lock(); |
82 | void unlock(); |
83 | |
84 | class ScopedLock |
85 | : public NonCopyable |
86 | { |
87 | public: |
88 | explicit ScopedLock(Spinlock& spinlock); |
89 | |
90 | private: |
91 | boost::detail::spinlock::scoped_lock m_lock; |
92 | }; |
93 | |
94 | private: |
95 | boost::detail::spinlock m_sp; |
96 | }; |
97 | |
98 | |
99 | // |
100 | // A read/write lock. |
101 | // |
102 | |
103 | struct NoWaitPolicy |
104 | { |
105 | static void pause(const uint32 iteration) {} |
106 | }; |
107 | |
108 | struct YieldWaitPolicy |
109 | { |
110 | static void pause(const uint32 iteration) { yield(); } |
111 | }; |
112 | |
113 | template <uint32 ms> |
114 | struct SleepWaitPolicy |
115 | { |
116 | static void pause(const uint32 iteration) { sleep(ms); } |
117 | }; |
118 | |
119 | template <typename WaitPolicy = NoWaitPolicy> |
120 | class ReadWriteLock |
121 | : public NonCopyable |
122 | { |
123 | public: |
124 | ReadWriteLock(); |
125 | |
126 | bool try_lock_read(); |
127 | void lock_read(); |
128 | void unlock_read(); |
129 | |
130 | bool try_lock_write(); |
131 | void lock_write(); |
132 | void unlock_write(); |
133 | |
134 | class ScopedReadLock |
135 | : public NonCopyable |
136 | { |
137 | public: |
138 | explicit ScopedReadLock(ReadWriteLock& lock); |
139 | ~ScopedReadLock(); |
140 | |
141 | private: |
142 | ReadWriteLock& m_lock; |
143 | }; |
144 | |
145 | class ScopedWriteLock |
146 | : public NonCopyable |
147 | { |
148 | public: |
149 | explicit ScopedWriteLock(ReadWriteLock& lock); |
150 | ~ScopedWriteLock(); |
151 | |
152 | private: |
153 | ReadWriteLock& m_lock; |
154 | }; |
155 | |
156 | private: |
157 | boost::atomic<uint32> m_readers; |
158 | boost::mutex m_mutex; |
159 | }; |
160 | |
161 | |
162 | // |
163 | // Process/thread priority levels. |
164 | // |
165 | |
166 | enum ProcessPriority |
167 | { |
168 | ProcessPriorityLowest, |
169 | ProcessPriorityLow, |
170 | ProcessPriorityNormal, |
171 | ProcessPriorityHigh, |
172 | ProcessPriorityHighest |
173 | }; |
174 | |
175 | |
176 | // |
177 | // An object to set the priority for the current process. |
178 | // |
179 | |
180 | class APPLESEED_DLLSYMBOL ProcessPriorityContext |
181 | : public NonCopyable |
182 | { |
183 | public: |
184 | // The constructor sets the priority of the current process. |
185 | ProcessPriorityContext( |
186 | const ProcessPriority priority, |
187 | Logger* logger = 0); |
188 | |
189 | // The destructor restores previous settings. |
190 | ~ProcessPriorityContext(); |
191 | |
192 | private: |
193 | struct Impl; |
194 | Impl* impl; |
195 | }; |
196 | |
197 | |
198 | // |
199 | // An object to set the priority for the current thread. |
200 | // |
201 | |
202 | class APPLESEED_DLLSYMBOL ThreadPriorityContext |
203 | : public NonCopyable |
204 | { |
205 | public: |
206 | // The constructor sets the priority of the current thread. |
207 | ThreadPriorityContext( |
208 | const ProcessPriority priority, |
209 | Logger* logger = 0); |
210 | |
211 | // The destructor restores previous settings. |
212 | ~ThreadPriorityContext(); |
213 | |
214 | private: |
215 | struct Impl; |
216 | Impl* impl; |
217 | }; |
218 | |
219 | |
220 | // |
221 | // An object to configure the current process and thread for accurate microbenchmarking. |
222 | // |
223 | |
224 | class APPLESEED_DLLSYMBOL BenchmarkingThreadContext |
225 | : public NonCopyable |
226 | { |
227 | public: |
228 | // The constructor enables the benchmarking mode. |
229 | explicit BenchmarkingThreadContext(Logger* logger = 0); |
230 | |
231 | // The destructor restores previous settings. |
232 | ~BenchmarkingThreadContext(); |
233 | |
234 | private: |
235 | struct Impl; |
236 | Impl* impl; |
237 | }; |
238 | |
239 | |
240 | // |
241 | // Wraps a thread function pointer into a callable thread function object. |
242 | // |
243 | |
244 | template <typename Function> |
245 | class ThreadFunctionWrapper |
246 | { |
247 | public: |
248 | explicit ThreadFunctionWrapper(Function* function) |
249 | : m_function(function) |
250 | { |
251 | } |
252 | |
253 | ThreadFunctionWrapper(const ThreadFunctionWrapper& rhs) |
254 | : m_function(rhs.m_function) |
255 | { |
256 | } |
257 | |
258 | void operator()() |
259 | { |
260 | (*m_function)(); |
261 | } |
262 | |
263 | private: |
264 | Function* m_function; |
265 | }; |
266 | |
267 | |
268 | // |
269 | // A cross-thread, cross-DLL boolean flag. |
270 | // |
271 | |
272 | class APPLESEED_DLLSYMBOL ThreadFlag |
273 | { |
274 | public: |
275 | // Constructor, clears the flag. |
276 | ThreadFlag(); |
277 | |
278 | // Clear the flag. |
279 | void clear(); |
280 | |
281 | // Set the flag. |
282 | void set(); |
283 | |
284 | // Check the flag. |
285 | bool is_clear() const; |
286 | bool is_set() const; |
287 | |
288 | private: |
289 | mutable volatile uint32 m_flag; |
290 | }; |
291 | |
292 | |
293 | // |
294 | // Spinlock class implementation. |
295 | // |
296 | |
297 | inline Spinlock::Spinlock() |
298 | { |
299 | // todo: is there a simpler way to initialize m_sp in a platform-independent manner? |
300 | boost::detail::spinlock initialized_sp = BOOST_DETAIL_SPINLOCK_INIT; |
301 | m_sp = initialized_sp; |
302 | } |
303 | |
304 | inline bool Spinlock::try_lock() |
305 | { |
306 | return m_sp.try_lock(); |
307 | } |
308 | |
309 | inline void Spinlock::lock() |
310 | { |
311 | m_sp.lock(); |
312 | } |
313 | |
314 | inline void Spinlock::unlock() |
315 | { |
316 | m_sp.unlock(); |
317 | } |
318 | |
319 | inline Spinlock::ScopedLock::ScopedLock(Spinlock& spinlock) |
320 | : m_lock(spinlock.m_sp) |
321 | { |
322 | } |
323 | |
324 | |
325 | // |
326 | // ReadWriteLock class implementation. |
327 | // |
328 | |
329 | template <typename WaitPolicy> |
330 | inline ReadWriteLock<WaitPolicy>::ReadWriteLock() |
331 | : m_readers(0) |
332 | { |
333 | } |
334 | |
335 | template <typename WaitPolicy> |
336 | inline bool ReadWriteLock<WaitPolicy>::try_lock_read() |
337 | { |
338 | // Make sure there are no writers active. |
339 | if (!m_mutex.try_lock()) |
340 | return false; |
341 | |
342 | // A new reader is active. |
343 | ++m_readers; |
344 | |
345 | // Let other readers start. |
346 | m_mutex.unlock(); |
347 | |
348 | return true; |
349 | } |
350 | |
351 | template <typename WaitPolicy> |
352 | inline void ReadWriteLock<WaitPolicy>::lock_read() |
353 | { |
354 | // Make sure there are no writers active. |
355 | m_mutex.lock(); |
356 | |
357 | // A new reader is active. |
358 | ++m_readers; |
359 | |
360 | // Let other readers start. |
361 | m_mutex.unlock(); |
362 | } |
363 | |
364 | template <typename WaitPolicy> |
365 | inline void ReadWriteLock<WaitPolicy>::unlock_read() |
366 | { |
367 | --m_readers; |
368 | } |
369 | |
370 | template <typename WaitPolicy> |
371 | inline bool ReadWriteLock<WaitPolicy>::try_lock_write() |
372 | { |
373 | // Make sure no reader can start. |
374 | if (!m_mutex.try_lock()) |
375 | return false; |
376 | |
377 | // Wait until active readers have terminated. |
378 | for (uint32 i = 0; m_readers > 0; ++i) |
379 | WaitPolicy::pause(i); |
380 | |
381 | return true; |
382 | } |
383 | |
384 | template <typename WaitPolicy> |
385 | inline void ReadWriteLock<WaitPolicy>::lock_write() |
386 | { |
387 | // Make sure no reader can start. |
388 | m_mutex.lock(); |
389 | |
390 | // Wait until active readers have terminated. |
391 | for (uint32 i = 0; m_readers > 0; ++i) |
392 | WaitPolicy::pause(i); |
393 | } |
394 | |
395 | template <typename WaitPolicy> |
396 | inline void ReadWriteLock<WaitPolicy>::unlock_write() |
397 | { |
398 | m_mutex.unlock(); |
399 | } |
400 | |
401 | template <typename WaitPolicy> |
402 | inline ReadWriteLock<WaitPolicy>::ScopedReadLock::ScopedReadLock(ReadWriteLock& lock) |
403 | : m_lock(lock) |
404 | { |
405 | m_lock.lock_read(); |
406 | } |
407 | |
408 | template <typename WaitPolicy> |
409 | inline ReadWriteLock<WaitPolicy>::ScopedReadLock::~ScopedReadLock() |
410 | { |
411 | m_lock.unlock_read(); |
412 | } |
413 | |
414 | template <typename WaitPolicy> |
415 | inline ReadWriteLock<WaitPolicy>::ScopedWriteLock::ScopedWriteLock(ReadWriteLock& lock) |
416 | : m_lock(lock) |
417 | { |
418 | m_lock.lock_write(); |
419 | } |
420 | |
421 | template <typename WaitPolicy> |
422 | inline ReadWriteLock<WaitPolicy>::ScopedWriteLock::~ScopedWriteLock() |
423 | { |
424 | m_lock.unlock_write(); |
425 | } |
426 | |
427 | |
428 | // |
429 | // ThreadFlag class implementation. |
430 | // |
431 | |
432 | inline ThreadFlag::ThreadFlag() |
433 | { |
434 | clear(); |
435 | } |
436 | |
437 | inline void ThreadFlag::clear() |
438 | { |
439 | atomic_write(&m_flag, 0); |
440 | } |
441 | |
442 | inline void ThreadFlag::set() |
443 | { |
444 | atomic_write(&m_flag, 1); |
445 | } |
446 | |
447 | inline bool ThreadFlag::is_clear() const |
448 | { |
449 | return atomic_read(&m_flag) == 0; |
450 | } |
451 | |
452 | inline bool ThreadFlag::is_set() const |
453 | { |
454 | return !is_clear(); |
455 | } |
456 | |
457 | } // namespace foundation |
458 | |
459 | #endif // !APPLESEED_FOUNDATION_PLATFORM_THREAD_H |
460 | |