Library of Assembled Shared Sources
thread_win32.inl
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2024 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#ifndef LASS_GUARDIAN_OF_INCLUSION_UTIL_IMPL_THREAD_WIN32_INL
44#define LASS_GUARDIAN_OF_INCLUSION_UTIL_IMPL_THREAD_WIN32_INL
45
46#include "../util_common.h"
47#include "../thread.h"
48#include "../singleton.h"
49#include "../environment.h"
50#include "../bit_manip.h"
51#include "lass_errno.h"
52
53#define NOMINMAX
54#define WIN32_LEAN_AND_MEAN
55#include <process.h>
56#include <windows.h>
57
58#if LASS_PLATFORM_TYPE == LASS_PLATFORM_TYPE_WIN32
59# pragma warning(push)
60# pragma warning(disable: 4239) // nonstandard extension used : 'argument' : conversion from 'T' to 'T&'
61# pragma warning(disable: 4267) // conversion from 'size_t' to 'unsigned int'
62# pragma warning(disable: 4996) // This function or variable may be unsafe ...
63#endif
64
65namespace lass
66{
67namespace util
68{
69namespace impl
70{
71
72TCpuSet availableProcessors()
73{
74 DWORD_PTR processAffinityMask, systemAffinityMask;
75 LASS_ENFORCE_WINAPI(GetProcessAffinityMask(GetCurrentProcess(), &processAffinityMask, &systemAffinityMask));
76
77 // determine number of processors (highest set bit of systemAffinityMask)
78 size_t n = 0;
79 while (systemAffinityMask)
80 {
81 ++n;
82 systemAffinityMask >>= 1;
83 }
84
85 // determine what processors are available to this process
86 TCpuSet cpuSet(n, false);
87 for (size_t i = 0; i < n; ++i)
88 {
89 cpuSet[i] = util::checkBit(processAffinityMask, i);
90 }
91
92 return cpuSet;
93}
94
95/** @internal
96 * @ingroup Threading
97 */
98class MutexInternal: NonCopyable
99{
100public:
101 MutexInternal():
102 lockCount_(0)
103 {
104 mutex_ = LASS_ENFORCE_WINAPI(::CreateMutex(NULL, FALSE, NULL));
105
106 }
107 ~MutexInternal()
108 {
109 LASS_ASSERT(lockCount_ == 0);
110 const BOOL ret = CloseHandle(mutex_);
111 LASS_ASSERT(ret != 0);
112 if (ret == 0)
113 {
114 const unsigned lastError = impl::lass_GetLastError();
115 std::cerr << "[LASS RUN MSG] WARNING: CloseHandle failed in ~MutexInternal(): ("
116 << lastError << ") " << impl::lass_FormatMessage(lastError) << std::endl;
117 }
118 }
119 void lock()
120 {
121 const DWORD ret = WaitForSingleObject(mutex_, INFINITE);
122 switch ( ret )
123 {
124 case WAIT_ABANDONED:
125 // if you get here, you have a design flaw. But we'll try to continue.
126 LASS_ASSERT(false);
127 std::cerr << "[LASS RUN MSG] UNDEFINED BEHAVIOUR WARNING: "
128 << "WaitForSingleObject returned with WAIT_ABANDONED. "
129 << "Going to take ownership and continue happily." << std::endl;
130 break;
131 case WAIT_OBJECT_0:
132 // ok
133 break;
134 case WAIT_FAILED:
135 {
136 const unsigned lastError = impl::lass_GetLastError();
137 LASS_THROW("WaitForSingleObject failed: (" << lastError << ") "
138 << impl::lass_FormatMessage(lastError));
139 }
140 case WAIT_TIMEOUT:
141 default:
142 LASS_THROW("impossible return value of WaitForSingleObject: " << ret);
143 }
144 ++lockCount_;
145 }
146 LockResult tryLock()
147 {
148 const DWORD ret = WaitForSingleObject(mutex_, 0);
149 switch ( ret )
150 {
151 case WAIT_ABANDONED:
152 // if you get here, you have a design flaw. But we'll try to continue.
153 LASS_ASSERT(false);
154 std::cerr << "[LASS RUN MSG] UNDEFINED BEHAVIOUR WARNING: "
155 << "WaitForSingleObject returned with WAIT_ABANDONED. "
156 << "Going to take ownership and continue happily." << std::endl;
157 break;
158 case WAIT_OBJECT_0:
159 // ok
160 break;
161 case WAIT_FAILED:
162 {
163 const unsigned lastError = impl::lass_GetLastError();
164 LASS_THROW("WaitForSingleObject failed: (" << lastError << ") "
165 << impl::lass_FormatMessage(lastError));
166 }
167 case WAIT_TIMEOUT:
168 return lockBusy;
169 default:
170 LASS_THROW("impossible return value of WaitForSingleObject: " << ret);
171 }
172 ++lockCount_;
173 return lockSuccess;
174 }
175 void unlock()
176 {
177 LASS_ASSERT(lockCount_ > 0);
178 if (lockCount_ == 0)
179 {
180 LASS_THROW("attempting to unlock an unlocked mutex");
181 }
182 --lockCount_;
183 LASS_ENFORCE_WINAPI(ReleaseMutex(mutex_));
184 }
185 unsigned lockCount() const
186 {
187 return lockCount_;
188 }
189private:
190 HANDLE mutex_;
191 unsigned lockCount_;
192};
193
194
195
196/** @internal
197 * @ingroup Threading
198 */
199class CriticalSectionInternal: NonCopyable
200{
201public:
202 CriticalSectionInternal()
203 {
204 InitializeCriticalSection(&criticalSection_);
205 }
206 ~CriticalSectionInternal()
207 {
208 LASS_ASSERT(criticalSection_.LockCount == -1);
209 DeleteCriticalSection(&criticalSection_);
210 }
211 void lock()
212 {
213 EnterCriticalSection(&criticalSection_);
214 }
215 LockResult tryLock()
216 {
217 return TryEnterCriticalSection(&criticalSection_) ? lockSuccess : lockBusy;
218 }
219 void unlock()
220 {
221 LASS_ASSERT(criticalSection_.RecursionCount > 0);
222 LeaveCriticalSection(&criticalSection_);
223 }
224 unsigned lockCount() const
225 {
226 return criticalSection_.RecursionCount;
227 }
228private:
229 CRITICAL_SECTION criticalSection_;
230};
231
232
233
234/** @internal
235 * @ingroup Threading
236 */
237class ConditionInternal: NonCopyable
238{
239public:
240 ConditionInternal():
241 threadsWaiting_(0)
242 {
243 event_ = LASS_ENFORCE_WINAPI(::CreateEvent(
244 NULL, // default secutiry
245 FALSE, // not manual reset
246 FALSE, // nonsignaled initially
247 NULL // nameless event
248 ));
249 }
250 ~ConditionInternal()
251 {
252 const BOOL ret = CloseHandle(event_);
253 LASS_ASSERT(ret != 0);
254 if (ret == 0)
255 {
256 const unsigned lastError = impl::lass_GetLastError();
257 std::cerr << "[LASS RUN MSG] UNDEFINED BEHAVIOUR WARNING: "
258 << " CloseHandle failed in ~MutexInternal(): ("
259 << lastError << ") " << impl::lass_FormatMessage(lastError) << std::endl;
260 }
261 }
262 void wait()
263 {
264 wait(INFINITE);
265 }
266 WaitResult wait(unsigned long iMilliSeconds)
267 {
268 // as m_nWaiters variable is accessed from multiple waiting threads
269 // (and possibly from the broadcasting thread), we need to change its
270 // value atomically
271 ::InterlockedIncrement(&threadsWaiting_);
272 // FIXME this should be MsgWaitForMultipleObjects() as we want to keep
273 // processing Windows messages while waiting (or don't we?)
274 const DWORD ret = WaitForSingleObject(event_, iMilliSeconds);
275 ::InterlockedDecrement(&threadsWaiting_);
276
277 switch ( ret )
278 {
279 case WAIT_OBJECT_0:
280 return waitSuccess;
281 case WAIT_TIMEOUT:
282 return waitTimeout;
283 case WAIT_FAILED:
284 {
285 const unsigned lastError = impl::lass_GetLastError();
286 LASS_THROW("WaitForSingleObject failed: (" << lastError << ") "
287 << impl::lass_FormatMessage(lastError));
288 }
289 default:
290 LASS_THROW("impossible return value of WaitForSingleObject: " << ret);
291 }
292 }
293 void signal()
294 {
295 // set the event to signaled: if a thread is already waiting on it, it
296 // will be woken up, otherwise the event will remain in the signaled
297 // state until someone waits on it. In any case, the system will return
298 // it to a non signalled state afterwards. If multiple threads are
299 // waiting, only one will be woken up.
300 LASS_ENFORCE_WINAPI(::SetEvent(event_));
301 }
302 void broadcast()
303 {
304 // we need to save the original value as m_nWaiters is goign to be
305 // decreased by the signalled thread resulting in the loop being
306 // executed less times than needed
307 LONG nWaiters = threadsWaiting_;
308
309 // this works because all these threads are already waiting and so each
310 // SetEvent() inside Signal() is really a PulseEvent() because the
311 // event state is immediately returned to non-signaled
312 for ( LONG n = 0; n < nWaiters; ++n )
313 signal();
314 }
315private:
316 HANDLE event_;
317 LONG threadsWaiting_;
318};
319
320
321
322/** @internal
323 * @ingroup Threading
324 */
325class ThreadLocalStorageInternal: NonCopyable
326{
327public:
328 typedef void (*TDestructor)(void*);
329
330 ThreadLocalStorageInternal(void (*destructor)(void*))
331 {
332 index_ = TlsAlloc();
333 if (index_ == TLS_OUT_OF_INDEXES)
334 {
335 const unsigned lastError = impl::lass_GetLastError();
336 LASS_THROW("Failed to allocate thread local storage: TlsAlloc failed: ("
337 << lastError << ") " << impl::lass_FormatMessage(lastError));
338 }
339 if (TDestructors* destrs = destructors())
340 {
341 try
342 {
343 (*destrs)[index_] = destructor;
344 }
345 catch (...)
346 {
347 freeSlot(index_);
348 throw;
349 }
350 }
351 else
352 {
353 freeSlot(index_);
354 LASS_THROW("Failed to allocate thread local storage: dead destructor singleton");
355 }
356 }
357 ~ThreadLocalStorageInternal()
358 {
359 if (TDestructors* destrs = destructors())
360 {
361 destrs->erase(index_);
362 }
363 freeSlot(index_);
364 }
365 void* get() const
366 {
367 void* result = TlsGetValue(index_); // intel700 doesn't like void* const here [Bramz]
368 if (result == 0)
369 {
370 // getting the value must be pretty fast at times, so only do this enforcer if
371 // result indicates that something _may_ be wrong [Bramz]
372 //
373 return LASS_ENFORCE_WINAPI(result)("Failed to get thread local storage value");
374 }
375 return result;
376 }
377 void set(void* value)
378 {
379 LASS_ENFORCE_WINAPI(TlsSetValue(index_, value))("Failed to set thread local storage value");
380 }
381
382 /** destruct the local copies of the variables in a thread.
383 * to be called at end of thread's life time ...
384 */
385 static void destructLocals()
386 {
387 if (TDestructors* destrs = destructors())
388 {
389 for (TDestructors::iterator i = destrs->begin(); i != destrs->end(); ++i)
390 {
391 DWORD index = i->first;
392 TDestructor destructor = i->second;
393 if (destructor)
394 {
395 if (void* p = TlsGetValue(index))
396 {
397 destructor(p);
398 TlsSetValue(index, 0);
399 }
400 }
401 }
402 }
403 }
404
405private:
406
407 typedef std::map<DWORD, TDestructor> TDestructors;
408
409 void freeSlot(DWORD index)
410 {
411 if (!TlsFree(index))
412 {
413 const unsigned lastError = impl::lass_GetLastError();
414 std::cerr << "[LASS RUN MSG] UNDEFINED BEHAVIOUR WARNING: TlsFree failed: ("
415 << lastError << ") " << impl::lass_FormatMessage(lastError) << std::endl;
416 }
417 }
418
419 static TDestructors* destructors()
420 {
422 }
423
424 static TDestructors* forceIntoExistance;
425
426 DWORD index_;
427};
428
429// MainLocalStorageDestroyer is going to need this singleton at exit-time. Make sure it exists [Bramz]
430ThreadLocalStorageInternal::TDestructors* ThreadLocalStorageInternal::forceIntoExistance =
431 ThreadLocalStorageInternal::destructors();
432
433
434
435/** @internal
436 * @ingroup Threading
437 * Abuse singleton mechanism to destroy the local thread storage instances of the main thread
438 * before the destructor map (which is a singleton as well) goes out of business. We do that
439 * by creating a helper singleton that has higher destruction priority
440 */
441class MainLocalStorageDestroyer: NonCopyable
442{
443public:
444 ~MainLocalStorageDestroyer()
445 {
446 ThreadLocalStorageInternal::destructLocals();
447 }
448private:
449 static MainLocalStorageDestroyer* forceIntoExistance;
450};
451
452MainLocalStorageDestroyer* MainLocalStorageDestroyer::forceIntoExistance =
454
455
456
457/** @internal
458 * @ingroup Threading
459 */
460DWORD_PTR bindThread(HANDLE thread, size_t processor)
461{
462 DWORD_PTR affinityMask = 0;
463 if (processor == Thread::anyProcessor)
464 {
465 DWORD_PTR processAffinityMask, systemAffinityMask;
466 LASS_ENFORCE_WINAPI(GetProcessAffinityMask(GetCurrentProcess(), &processAffinityMask, &systemAffinityMask));
467 affinityMask = systemAffinityMask;
468 }
469 else
470 {
471 util::setBit(affinityMask, processor);
472 }
473 LASS_ENFORCE_WINAPI(SetThreadAffinityMask(thread, affinityMask))
474 ("Failed to bind thread to processor ")(processor);
475 // do it again, to confirm the set affinity
476 const DWORD_PTR threadAffinity = LASS_ENFORCE_WINAPI(SetThreadAffinityMask(thread, affinityMask));
477 LASS_ENFORCE(threadAffinity == affinityMask);
478 return threadAffinity;
479}
480
481void setThreadName(DWORD threadId, const char* threadName)
482{
483 char buffer[10];
484 strncpy(buffer, threadName, 10);
485 buffer[9] = '\0';
486
487 struct THREADNAME_INFO
488 {
489 DWORD dwType; // must be 0x1000
490 LPCSTR szName; // pointer to name (in user addr space)
491 DWORD dwThreadID; // thread ID (-1=caller thread)
492 DWORD dwFlags; // reserved for future use, must be zero
493 };
494
495 THREADNAME_INFO info;
496 info.dwType = 0x1000;
497 info.szName = threadName;
498 info.dwThreadID = threadId;
499 info.dwFlags = 0;
500
501 __try
502 {
503 RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info);
504 }
505 __except(EXCEPTION_CONTINUE_EXECUTION)
506 {
507 }
508}
509
510
511
512#define LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(exception_type)\
513 catch (const exception_type& error)\
514 {\
515 pimpl->error_.reset(new RemoteExceptionWrapper<exception_type>(error));\
516 }
517
518/** @internal
519 * @ingroup Threading
520 */
521class ThreadInternal: NonCopyable
522{
523public:
524
525 ThreadInternal(Thread& iThread, ThreadKind iKind, const char* name):
526 thread_(iThread),
527 name_(name),
528 isJoinable_(iKind == threadJoinable),
529 isCreated_(false)
530 {
531 }
532
533 ~ThreadInternal()
534 {
535 }
536
537 /** run thread.
538 */
539 void run()
540 {
541 if (isCreated_)
542 {
543 LASS_THROW("You can run a thread only once");
544 }
545 handle_ = (HANDLE) _beginthreadex(NULL, 0, &ThreadInternal::startThread, this, 0, &id_);
546 if (handle_ == 0)
547 {
548 const int errnum = lass_errno();
549 LASS_THROW("_beginthreadex failed: (" << errnum << ") " << lass_strerror(errnum));
550 }
551 if (name_)
552 {
553 setThreadName(id_, name_);
554 }
555 runCondition_.wait();
556 }
557
558 bool isJoinable() const
559 {
560 return isJoinable_ && isCreated_;
561 }
562
563 void join()
564 {
565 if (!isJoinable())
566 {
567 LASS_THROW("Can not wait for uncreated or detached threads");
568 }
569 const DWORD ret = WaitForSingleObject(handle_, INFINITE);
570 switch ( ret )
571 {
572 case WAIT_OBJECT_0:
573 // ok
574 break;
575 case WAIT_FAILED:
576 {
577 const unsigned lastError = impl::lass_GetLastError();
578 LASS_THROW("WaitForSingleObject failed: (" << lastError << ") "
579 << impl::lass_FormatMessage(lastError));
580 }
581 default:
582 LASS_THROW("impossible return value of WaitForSingleObject: " << ret);
583 }
584 isJoinable_ = false;
585 if (error_.get())
586 {
587 error_->throwSelf();
588 }
589 }
590
591 void bind(size_t processor)
592 {
593 affinity_ = bindThread(handle_, processor);
594 }
595
596 const TCpuSet affinity() const
597 {
598 const size_t n = numberOfProcessors();
599 TCpuSet result(numberOfProcessors(), false);
600 for (size_t i = 0; i < n; ++i)
601 {
602 result[i] = util::checkBit(affinity_, i);
603 }
604 return result;
605 };
606
607 static void sleep(unsigned long iMilliSeconds)
608 {
609 Sleep(iMilliSeconds);
610 }
611
612 static void yield()
613 {
614 Sleep(0);
615 }
616
617 static void bindCurrent(size_t processor)
618 {
619 bindThread(GetCurrentThread(), processor);
620 }
621
622 // thread function
623 static unsigned __stdcall startThread(void* iPimpl)
624 {
625 LASS_ASSERT(iPimpl);
626 ThreadInternal* pimpl = static_cast<ThreadInternal*>(iPimpl);
627 DWORD_PTR dummy;
628 GetProcessAffinityMask(GetCurrentProcess(), &pimpl->affinity_, &dummy);
629 pimpl->isCreated_ = true;
630 if (pimpl->isJoinable_)
631 {
632 try
633 {
634 pimpl->runCondition_.signal();
635 pimpl->thread_.doRun();
636 }
637 catch (const RemoteExceptionBase& error)
638 {
639 pimpl->error_ = error.clone();
640 }
641 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::domain_error)
642 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::invalid_argument)
643 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::length_error)
644 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::out_of_range)
645 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::range_error)
646 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::overflow_error)
647 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::underflow_error)
648 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::runtime_error)
649 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::logic_error)
650 LASS_UTIL_THREAD_WIN32_CATCH_AND_WRAP(::std::exception)
651 }
652 else
653 {
654 pimpl->runCondition_.signal();
655 pimpl->thread_.doRun();
656 delete &pimpl->thread_;
657 }
658 return 0;
659 }
660
661 static void onThreadDetach()
662 {
663 ThreadLocalStorageInternal::destructLocals();
664 }
665
666private:
667
668 Thread& thread_;
669 HANDLE handle_; // handle of the thread
670 DWORD_PTR affinity_;
671 TRemoteExceptionBasePtr error_;
672 Condition runCondition_;
673 const char* name_;
674 unsigned id_;
675 volatile bool isJoinable_;
676 volatile bool isCreated_;
677};
678
679
680}
681}
682}
683
684#if LASS_PLATFORM_TYPE == LASS_PLATFORM_TYPE_WIN32
685# pragma warning(pop)
686#endif
687
688#ifdef LASS_BUILD_DLL
689
690#include "../dll/dll_main.h"
691
692namespace lass
693{
694namespace util
695{
696namespace impl
697{
698
699BOOL threadDllMain(HINSTANCE, DWORD dwReason, LPVOID)
700{
701 switch (dwReason)
702 {
703 case DLL_PROCESS_DETACH:
704 case DLL_THREAD_DETACH:
705 lass::util::impl::ThreadInternal::onThreadDetach();
706 break;
707 }
708 return TRUE;
709}
710}
711}
712}
713
714LASS_EXECUTE_BEFORE_MAIN_EX(
715 lassUtilImplThreadWin32RegisterDllMain,
716 ::lass::dll::registerDllMain(::lass::util::impl::threadDllMain);
717)
718
719#else
720
721namespace
722{
723
724void NTAPI lassOnThreadCallback(PVOID, DWORD dwReason, PVOID)
725{
726 if(dwReason == DLL_THREAD_DETACH)
727 {
728 lass::util::impl::ThreadInternal::onThreadDetach();
729 }
730}
731
732#if _MSC_VER >= 1400 // VC8.0
733# pragma section(".CRT$XLB", read)
734#else
735# pragma section(".CRT$XLB", read, write)
736#endif
737
738extern "C" __declspec(allocate(".CRT$XLB")) PIMAGE_TLS_CALLBACK lassThreadCallback = lassOnThreadCallback;
739
740}
741
742#ifdef _WIN64
743# pragma comment(linker, "/INCLUDE:_tls_used")
744#else
745# pragma comment(linker, "/INCLUDE:__tls_used")
746#endif
747
748#endif
749
750
751
752#endif
753
754// EOF
755
static TInstance * instance()
Return pointer to singleton instance.
Definition singleton.inl:82
static constexpr size_t anyProcessor
argument for Thread::bind to unbind the thread so it runs on any processor
Definition thread.h:210
bool checkBit(T a_bits, size_t a_bit)
return true if a bit is set high.
void setBit(T &a_bits, size_t a_bit)
Set a bit high.
Definition bit_manip.inl:66
const std::string lass_strerror(int errnum)
returns message associated to an CLIB error code
int lass_errno()
returns CLIB errno
ThreadKind
ThreadKind.
Definition thread.h:109
LockResult
Return code for lock functions.
Definition thread.h:89
size_t numberOfProcessors()
Return highest id of processor + 1, in this machine.
Definition thread.cpp:74
WaitResult
Return code for wait functions.
Definition thread.h:99
@ threadJoinable
joinable thread, can be waited for
Definition thread.h:111
@ lockSuccess
Mutex/CriticalSection is succesfully locked by this thread.
Definition thread.h:90
@ lockBusy
Mutex/CriticalSection is locked by another thread.
Definition thread.h:91
@ waitSuccess
Wait is successfully terminated.
Definition thread.h:100
@ waitTimeout
Wait failed because of a timeout.
Definition thread.h:101
general utility, debug facilities, ...
Library for Assembled Shared Sources.
Definition config.h:53