CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
ThreadManager.cpp
Go to the documentation of this file.
1
15#include "ThreadManager.h"
16#include "PsyTime.h"
17#include "Utils.h"
18#include "UnitTestFramework.h"
19
20namespace cmlabs {
21
23
25 ThreadManager* threadManager = new ThreadManager();
26 if (!threadManager->init()) {
27 //fprintf(stderr, "ThreadManager init() failed...\n");
28 delete(threadManager);
29 return false;
30 }
31 return true;
32}
33
34// Static
35// Create a new thread and start it
36bool ThreadManager::CreateThread(THREAD_FUNCTION func, void* args, uint32& newID, uint32 reqID) {
39 return false;
40 return ThreadManager::Singleton->createThread(func, args, newID, reqID);
41}
42
43// Static
44// Pause the thread wherever it is now
48 return false;
49 return ThreadManager::Singleton->pauseThread(id);
50}
51
52// Static
53// Allow the thread to continue running
57 return false;
58 return ThreadManager::Singleton->continueThread(id);
59}
60
61// Static
62// Terminate the thread and restart it from its base location
66 return false;
67 return ThreadManager::Singleton->interruptThread(id);
68}
69
70// Static
71// Terminate and destroy the thread
75 return false;
76 return ThreadManager::Singleton->terminateThread(id);
77}
78
79
80// Static
81// Terminate and destroy the thread
85 return false;
86 return ThreadManager::Singleton->isThreadRunning(id);
87}
88
89
90// Static
91// Add thread stats for local thread - needed for pthreads
95 return false;
96 return ThreadManager::Singleton->addLocalThreadStats();
97}
98
99// Static
100// Add thread stats for local thread - needed for pthreads
103 if (!CreateThreadManager())
104 return false;
105 return ThreadManager::Singleton->getLocalThreadID(id);
106}
107
108
109// Static
110// Get the statistics for local thread
112 ThreadStats stats;
113 stats.created = 0;
115 if (!CreateThreadManager())
116 return stats;
117 uint32 id;
119 return stats;
120 return ThreadManager::Singleton->getThreadStats(id);
121}
122
123// Static
124// Get the statistics for a thread
126 ThreadStats stats;
127 stats.created = 0;
129 if (!CreateThreadManager())
130 return stats;
131 return ThreadManager::Singleton->getThreadStats(id);
132}
133
134// Static
135// Get the statistics for all threads
138 if (!CreateThreadManager())
139 return NULL;
140 return ThreadManager::Singleton->getAllThreadStats(count);
141}
142
143// Static
144// Shutdown and delete the ThreadManager
147 return true;
149 return true;
152 return (ThreadManager::Singleton == NULL);
153}
154
155
156
157
159 // Initially allocate space for 1024 threads
160 data = NULL;
162 shouldContinue = true;
163 isRunning = false;
164}
165
167 if (ThreadManager::Singleton == NULL)
168 return;
169 if (!mutex.enter())
170 return;
171 shutdown();
172 // Deallocate data
174 free(data);
175 data = NULL;
176 mutex.leave();
177}
178
180 if (!mutex.enter())
181 return false;
182 // Startup the root thread for Windows only
183 uint32 newID = 0;
185 if ((!createThread(ThreadMonitoring, NULL, newID, 0)) || (newID != 0)) {
186 mutex.leave();
187 return false;
188 }
190 header->activeCount = 1;
191 mutex.leave();
192 return true;
193}
194
196 stop();
197 if (!mutex.enter())
198 return false;
199 // Terminate all threads
201 ThreadStats* stats = GETTHREADSTATS(data,0);
202 for (uint32 n=0; n<header->count; n++) {
203 if (stats->status > THREAD_TERMINATED) {
204 terminateThread(stats->id);
205 }
206 stats += 1;
207 }
208 header->activeCount = 0;
209 mutex.leave();
210 return true;
211}
212
213
214
215
216// Create a new thread and start it
217bool ThreadManager::createThread(THREAD_FUNCTION func, void* args, uint32& newID, uint32 reqID) {
218// fprintf(stderr, "ThreadManager creating thread enter...\n");
219 if (!mutex.enter())
220 return false;
221// fprintf(stderr, "ThreadManager creating thread got mutex...\n");
223 if (header->activeCount == header->count) {
224 // we need to resize Thread storage
225 if (!resizeThreadStorage(header->count * 4)) {
226 mutex.leave();
227 return false;
228 }
229 header = (ThreadDataHeader*)data;
230 }
231
232// fprintf(stderr, "ThreadManager creating thread 1...\n");
233 ThreadStats* stats;
234 if (reqID == 0) {
235 // A bit cheeky as root = 0, but it works well for the root thread as this is the first one requested anyway.
236 if (!utils::GetFirstFreeBitLoc(((const char*)data+sizeof(ThreadDataHeader)), header->bitFieldSize, newID) || (newID >= header->count)) {
237 mutex.leave();
238 return false;
239 }
240 stats = GETTHREADSTATS(data, newID);
241 stats->id = newID;
242 }
243 else {
244 newID = reqID;
245 stats = GETTHREADSTATS(data, newID);
246 stats->id = newID;
247 }
248
249// fprintf(stderr, "ThreadManager creating thread 2...\n");
250 stats->created = GetTimeNow();
251 stats->status = THREAD_INIT;
252 // We assume the stats are 0 already
253 // Create the thread
254 if (!utils::CreateThread(func, args, stats->hThread, stats->osID)) {
255 newID = 0;
256 stats->created = 0;
257 stats->status = THREAD_NONE;
258 mutex.leave();
259 return false;
260 }
261
262 // printf("Create thread %u = %u\n", stats->id, (uint32)stats->hThread);
263 utils::SetBit(stats->id, BITOCCUPIED, ((char*)data+sizeof(ThreadDataHeader)), header->bitFieldSize);
264 stats->func = func;
265 stats->args = args;
266 stats->status = THREAD_RUNNING;
267 header->activeCount++;
268 //fprintf(stderr, "ThreadManager creating thread ID %u, trace:\n%s", stats->osID);
269 //fprintf(stderr, "ThreadManager creating thread ID %u, trace:\n%s", stats->osID, utils::PrintProgramTrace(3, 6).c_str());
270 mutex.leave();
271// fprintf(stderr, "ThreadManager creating thread 4...\n");
272 return true;
273}
274
275// Pause the thread wherever it is now
277 if (!mutex.enter())
278 return false;
279 ThreadStats* stats = GETTHREADSTATS(data, id);
280 // Pause the thread
281 if (!utils::PauseThread(stats->hThread)) {
282 mutex.leave();
283 return false;
284 }
285 stats->status = THREAD_PAUSED;
286 mutex.leave();
287 return true;
288}
289
290// Allow the thread to continue running
292 if (!mutex.enter())
293 return false;
294 ThreadStats* stats = GETTHREADSTATS(data, id);
295 // Continue the thread
296 if (!utils::ContinueThread(stats->hThread)) {
297 mutex.leave();
298 return false;
299 }
300 stats->status = THREAD_RUNNING;
301 mutex.leave();
302 return true;
303}
304
305// Terminate the thread and restart it from its base location
307 if (!mutex.enter())
308 return false;
309 ThreadStats* stats = GETTHREADSTATS(data, id);
310 stats->status = THREAD_INTERRUPTED;
311 // Terminate the thread
312 // Terminate the thread
313 if (!utils::TerminateThread(stats->hThread)) {
314 mutex.leave();
315 return false;
316 }
318 header->activeCount--; // it will be increased again in createThread
319 // Create the thread again
320 uint32 newID = 0;
321 if (!createThread(stats->func, stats->args, newID, id)) {
322 stats->status = THREAD_TERMINATED;
323 stats->func = NULL;
324 stats->created = 0;
325 header->activeCount--;
326 mutex.leave();
327 return false;
328 }
329 mutex.leave();
330 return false;
331}
332
333// Terminate and destroy the thread
335 if (!mutex.enter())
336 return false;
338 ThreadStats* stats = GETTHREADSTATS(data, id);
339 // Terminate the thread
340 // printf("Terminate thread %u = %u\n", stats->id, (uint32)stats->hThread);
342 //if (utils::IsThreadRunning(stats->hThread)) {
343 //if (!utils::TerminateThread(stats->hThread)) {
344 // mutex.leave();
345 // return false;
346 //}
347 //}
348 stats->status = THREAD_TERMINATED;
349 stats->func = NULL;
350 stats->created = 0;
351 header->activeCount--;
352 mutex.leave();
353 return true;
354}
355
356
357// Has the thread terminated or exited
359 if (!mutex.enter())
360 return false;
362 ThreadStats* stats = GETTHREADSTATS(data, id);
363 if (stats->hThread == 0) {
364 mutex.leave(); // was leaking the manager mutex on this early return
365 return false;
366 }
367 bool res = utils::IsThreadRunning(stats->hThread);
368 mutex.leave();
369 return res;
370}
371
372
373// Get the statistics for a thread
375 ThreadStats stats;
376 stats.created = 0;
377 if (!mutex.enter()) {
378 mutex.leave();
379 return stats;
380 }
381 stats = *GETTHREADSTATS(data, id);
382 mutex.leave();
383 return stats;
384}
385
386// Get the statistics for all threads
388 if (!mutex.enter())
389 return NULL;
391 count = header->activeCount;
392 ThreadStats* allStats = new ThreadStats[count];
393 uint32 p=0;
394 ThreadStats* stats = GETTHREADSTATS(data,0);
395 for (uint32 n=0; n<header->count; n++) {
396 if (stats->status > THREAD_TERMINATED)
397 allStats[p++] = *stats;
398 stats += 1;
399 if (p >= count) break;
400 }
401 mutex.leave();
402 return allStats;
403}
404
405// Resize the ThreadStats storage to be able to contain more threads
407
408 uint32 bitFieldSize = utils::Calc32BitFieldSize(newCount);
409 uint32 size = sizeof(ThreadDataHeader) + bitFieldSize + newCount*sizeof(ThreadStats);
410 unsigned char *newData = (unsigned char*) malloc(size);
411 memset(newData, 0, size);
412 ThreadDataHeader* header = (ThreadDataHeader*) newData;
413 header->bitFieldSize = bitFieldSize;
414 header->count = newCount;
415 header->size = size;
417
418 uint32 statsOffset = sizeof(header->size)+sizeof(header->count)+sizeof(header->activeCount)+sizeof(header->statColPolicy);
419
420 if (data == NULL) {
421 header->activeCount = 0;
422 // Set summary stats to 0
423 memset(newData+statsOffset, 0, 10*2*(sizeof(uint64)));
424 // Set the bitfield to unused
425 memset(newData+sizeof(ThreadDataHeader), 255, bitFieldSize);
426 // Set all thread stats to 0
427 ThreadStats* firstStat = GETTHREADSTATS(newData,0);
428 memset(firstStat, 0, newCount*sizeof(ThreadStats));
429 }
430 else {
431 ThreadDataHeader* oldHeader = (ThreadDataHeader*) data;
432 header->activeCount = oldHeader->activeCount;
433 // Copy old summary stats
434 memcpy(newData+statsOffset, data+statsOffset, 10*2*(sizeof(uint64)));
435 // Initially, set the bitfield to unused
436 memset(newData+sizeof(ThreadDataHeader), 255, bitFieldSize);
437 // Copy old bitfield
438 memcpy(newData+sizeof(ThreadDataHeader), data+sizeof(ThreadDataHeader), oldHeader->bitFieldSize);
439 ThreadStats* firstStat = GETTHREADSTATS(newData,0);
440 ThreadStats* oldFirstStat = GETTHREADSTATS(data,0);
441 memcpy(firstStat, oldFirstStat, oldHeader->count*sizeof(ThreadStats));
442 free(data);
443 }
444 data = newData;
445 return true;
446}
447
448// Add thread stats for local thread - needed for pthreads
450 if (!mutex.enter())
451 return false;
452
453 uint32 id = 0;
454 if (!getLocalThreadID(id)) {
455 mutex.leave();
456 return false;
457 }
458 ThreadStats* stats = GETTHREADSTATS(data,id);
459 if ( (stats == NULL) || (stats->created == 0) ) {
460 mutex.leave();
461 return false;
462 }
463
464 uint32 timeOffset = sizeof(stats->id) + sizeof(stats->created) + sizeof(stats->status)
465 + sizeof(stats->hThread) + sizeof(stats->func) + sizeof(stats->args);
466 uint32 cpuUsageOffset = timeOffset + 10 * sizeof(uint64);
467
468 ThreadDataHeader* header = NULL;
469
470// uint32 headerTimeOffset = sizeof(header->size)+sizeof(header->count)+sizeof(header->activeCount)+sizeof(header->statColPolicy);
471// uint32 headerCPUUsageOffset = headerTimeOffset + 10 * sizeof(uint64);
472
473 // Shift the previously stored values Time
474 memcpy(stats+timeOffset, stats+timeOffset+sizeof(uint64), 9*sizeof(uint64));
475 // CPU Usage
476 memcpy(stats+cpuUsageOffset, stats+cpuUsageOffset+sizeof(uint64), 9*sizeof(uint64));
477 // Read current stats from the thread
478 if (utils::GetCPUTicks(stats->currentCPUTicks[0])) {
479 stats->time[0] = GetTimeNow();
480 // add these to the header stat sums
481 header = (ThreadDataHeader*) data;
482 header->time[0] = stats->time[0];
483 header->currentCPUTicks[0] += stats->currentCPUTicks[0];
484 // but do not rotate
485 }
486 else {
487 stats->time[0] = stats->currentCPUTicks[0] = 0;
488 mutex.leave();
489 return false;
490 }
491
492 mutex.leave();
493 return true;
494}
495
496// Get the local thread ID
498 if (!mutex.enter())
499 return false;
500
501 uint32 osID;
503 mutex.leave();
504 return false;
505 }
507
508 ThreadStats* stats = GETTHREADSTATS(data,0);
509 for (uint32 n=0; n<header->count; n++) {
510 if (stats->osID == osID) {
511 id = stats->id;
512 mutex.leave();
513 return true;
514 }
515 stats += 1;
516 }
517
518 // for pthreads one has to delete the handle afterwards
519 //#ifndef WINDOWS
520 // delete(hThread);
521 //#endif
522 mutex.leave();
523 return false;
524}
525
526
527// Root Thread, for Windows only until pthreads support getrusage from other threads
529
530 #ifdef WINDOWS
531 #else
532 #ifndef Darwin
533 sigset_t cancel;
534 sigemptyset(&cancel);
535 sigaddset(&cancel, SIGQUIT);
536 pthread_sigmask(SIG_UNBLOCK, &cancel, NULL);
537 #endif
538 #endif
539
541 return 0;
542
543 thread_ret_val(ThreadManager::Singleton->threadMonitoring());
544}
545
547
548 isRunning = true;
549
550 ThreadStats* stats = NULL;
551 uint32 timeOffset = sizeof(stats->id) + sizeof(stats->created) + sizeof(stats->status)
552 + sizeof(stats->hThread) + sizeof(stats->func) + sizeof(stats->args);
553 uint32 cpuUsageOffset = timeOffset + 10 * sizeof(uint64);
554
556 if (header->statColPolicy == THREAD_STATS_OFF) {
557 isRunning = false;
558 return 0; // do not continue as nothing is done anyway
559 }
560
561 uint32 headerTimeOffset = sizeof(header->size)+sizeof(header->count)+sizeof(header->activeCount)+sizeof(header->statColPolicy);
562 uint32 headerCPUUsageOffset = headerTimeOffset + 10 * sizeof(uint64);
563
564 uint64 sumUsage;
565 uint32 interval = 1000000, n;
566
567 uint64 lastCalc = 0, t;
568 while (shouldContinue) {
569 if ( (t = GetTimeNow()) - lastCalc > interval ) {
570 lastCalc = t;
571
572 if (!mutex.enter()) {
573 isRunning = false;
574 return -1;
575 }
576
577 header = (ThreadDataHeader*) data;
578 stats = GETTHREADSTATS(data,0);
579
580 if (header->statColPolicy == THREAD_STATS_ADHOC) {
581 // Threads have to call the stat gathering themselves
582 // so we just rotate the global stats
583
584 for (n=0; n<header->count; n++) {
585 if (stats->status > THREAD_TERMINATED) {
586 // check if it is still actually running
588 stats->status = THREAD_TERMINATED;
589 LogPrint(0, LOG_PROCESS, 2, "Thread finished: ID %u (OSID %u) age: %s", stats->id, stats->osID, PrintTimeDifString((uint32)GetTimeAge(stats->created)).c_str());
590 }
591 }
592 stats += 1;
593 }
594
595 // Shift the previously stored values Time
596 memcpy(header+headerTimeOffset, header+headerTimeOffset+sizeof(uint64), 9*sizeof(uint64));
597 // CPU Usage
598 memcpy(header+headerCPUUsageOffset, header+headerCPUUsageOffset+sizeof(uint64), 9*sizeof(uint64));
599
600 header->time[0] = header->currentCPUTicks[0] = 0;
601 }
602 else {
603 // We have to gather the stats, sum them up and rotate all buffers
604 sumUsage = 0;
605
606 for (n=0; n<header->count; n++) {
607 if (stats->status > THREAD_TERMINATED) {
608 // Shift the previously stored values Time
609 memcpy(stats+timeOffset, stats+timeOffset+sizeof(uint64), 9*sizeof(uint64));
610 // CPU Usage
611 memcpy(stats+cpuUsageOffset, stats+cpuUsageOffset+sizeof(uint64), 9*sizeof(uint64));
612 // Read current stats from the thread
613 if (utils::GetCPUTicks(stats->hThread, stats->currentCPUTicks[0])) {
614 stats->time[0] = GetTimeNow();
615 sumUsage += stats->currentCPUTicks[0];
616 }
617 else {
618 stats->time[0] = stats->currentCPUTicks[0] = 0;
619 }
620 // check if it is still actually running
622 stats->status = THREAD_TERMINATED;
623 LogPrint(0, LOG_PROCESS, 2, "Thread finished: ID %u (OSID %u) age: %s", stats->id, stats->osID, PrintTimeDifString((uint32)GetTimeAge(stats->created)).c_str());
624 }
625 }
626 stats += 1;
627 }
628
629 // Shift the previously stored values Time
630 memcpy(header+headerTimeOffset, header+headerTimeOffset+sizeof(uint64), 9*sizeof(uint64));
631 // CPU Usage
632 memcpy(header+headerCPUUsageOffset, header+headerCPUUsageOffset+sizeof(uint64), 9*sizeof(uint64));
633
634 header->time[0] = GetTimeNow();
635 header->currentCPUTicks[0] = sumUsage;
636 }
637
638 mutex.leave();
639 }
640
641 utils::Sleep(100);
642 }
643
644 isRunning = false;
645 return 0;
646}
647
648
649// ################# Unit test #################
650
651namespace {
652
653// Shared state for the ThreadManager unit test. Each worker thread does a
654// bounded amount of work (a fixed number of increments), accumulates its
655// result into a shared total under a mutex, then marks itself finished.
656struct TMTestState {
657 utils::Mutex mutex;
658 uint64 total; // sum of all per-thread work, protected by mutex
659 uint32 finishedCount; // number of worker threads that ran to completion
660 TMTestState() : total(0), finishedCount(0) {}
661};
662
663// Each worker performs this many increments. Kept modest so the whole test
664// finishes in well under a second, but large enough to be measurable.
665static const uint64 TM_ITERATIONS_PER_THREAD = 200000ULL;
666
667static THREAD_RET THREAD_FUNCTION_CALL TMTestWorker(THREAD_ARG arg) {
668 TMTestState* state = (TMTestState*)arg;
669 uint64 localSum = 0;
670 for (uint64 i = 0; i < TM_ITERATIONS_PER_THREAD; i++)
671 localSum += 1;
672 if (state->mutex.enter()) {
673 state->total += localSum;
674 state->finishedCount++;
675 state->mutex.leave();
676 }
678}
679
680} // anonymous namespace
681
683
684 // 1. Bring up the ThreadManager singleton.
685 unittest::progress(5, "create thread manager");
687 unittest::fail("ThreadManager test: CreateThreadManager() failed");
688 return false;
689 }
690
691 const uint32 NUMTHREADS = 8;
692 TMTestState state;
693 uint32 ids[NUMTHREADS];
694 for (uint32 n = 0; n < NUMTHREADS; n++)
695 ids[n] = 0;
696
697 // 2. Start the worker threads.
698 unittest::progress(20, "start worker threads");
699 uint64 t0 = GetTimeNow();
700 uint32 started = 0;
701 for (uint32 n = 0; n < NUMTHREADS; n++) {
702 if (!ThreadManager::CreateThread(TMTestWorker, &state, ids[n])) {
703 unittest::fail("ThreadManager test: CreateThread() failed for worker %u", n);
705 return false;
706 }
707 unittest::detail("started worker %u with id %u", n, ids[n]);
708 started++;
709 }
710 if (started != NUMTHREADS) {
711 unittest::fail("ThreadManager test: started %u of %u threads", started, NUMTHREADS);
713 return false;
714 }
715
716 // 3. Wait (bounded) for every worker to finish its work. We poll the shared
717 // finished counter and also the OS-level running state, with a hard
718 // timeout so the test can never hang.
719 unittest::progress(50, "wait for workers");
720 const uint32 TIMEOUTMS = 5000;
721 uint32 waitedMs = 0;
722 bool allFinished = false;
723 while (waitedMs < TIMEOUTMS) {
724 uint32 done = 0;
725 if (state.mutex.enter()) {
726 done = state.finishedCount;
727 state.mutex.leave();
728 }
729 if (done >= NUMTHREADS) {
730 allFinished = true;
731 break;
732 }
733 utils::Sleep(5);
734 waitedMs += 5;
735 }
736
737 if (!allFinished) {
738 unittest::fail("ThreadManager test: only %u of %u workers finished within %ums",
739 state.finishedCount, NUMTHREADS, TIMEOUTMS);
741 return false;
742 }
743
744 // 4. Verify the threads actually all ran and produced the expected total.
745 unittest::progress(75, "verify results");
746 uint64 expected = (uint64)NUMTHREADS * TM_ITERATIONS_PER_THREAD;
747 if (state.total != expected) {
748 unittest::fail("ThreadManager test: total work %llu != expected %llu",
749 state.total, expected);
751 return false;
752 }
753 if (state.finishedCount != NUMTHREADS) {
754 unittest::fail("ThreadManager test: finishedCount %u != %u",
755 state.finishedCount, NUMTHREADS);
757 return false;
758 }
759
760 // 5. Sanity-check the statistics API for one of the threads.
762 if (stats.created == 0) {
763 unittest::fail("ThreadManager test: GetThreadStats returned no creation time for id %u", ids[0]);
765 return false;
766 }
767
768 double us = (double)(GetTimeNow() - t0);
769 if (us > 0.0) {
770 unittest::metric("threads_per_sec", (double)NUMTHREADS / us * 1e6, "threads/s", true);
771 unittest::metric("total_run_time", us, "us", false);
772 }
773
774 // 6. Tear everything down cleanly. No threads remain after this point.
775 unittest::progress(90, "shutdown");
777 unittest::fail("ThreadManager test: Shutdown() failed");
778 return false;
779 }
780
781 unittest::progress(100, "done");
782 return true;
783}
784
787 "Thread manager create/run/join of multiple worker threads", "core");
788}
789
790} // namespace cmlabs
CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).
Process-wide thread registry and lifecycle manager: the concurrency core of CMSDK.
#define BITOCCUPIED
Definition Types.h:34
Small, dependency-free unit test harness used by all CMSDK object tests.
Cross-platform utility toolbox for CMSDK: threading, synchronization, shared memory,...
#define THREAD_NONE
Definition Utils.h:93
#define thread_ret_val(ret)
Definition Utils.h:131
#define THREAD_STATS_ADHOC
Definition Utils.h:102
#define THREAD_STATS_OFF
Definition Utils.h:100
#define LOG_PROCESS
Definition Utils.h:200
#define THREAD_RET
Definition Utils.h:127
#define THREAD_TERMINATED
Definition Utils.h:94
#define THREAD_FUNCTION_CALL
Definition Utils.h:129
#define LogPrint
Definition Utils.h:313
#define THREAD_PAUSED
Definition Utils.h:97
#define THREAD_RUNNING
Definition Utils.h:96
THREAD_RET(* THREAD_FUNCTION)(void *)
Definition Utils.h:128
#define THREAD_INTERRUPTED
Definition Utils.h:98
#define THREAD_INIT
Definition Utils.h:95
#define THREAD_ARG
Definition Utils.h:130
bool isRunning
Set by the worker while its loop is active.
virtual bool stop(uint32 timeout=200)
Ask the worker loop to finish and wait for it to do so.
bool shouldContinue
Loop-continuation flag; cleared by stop().
Singleton registry that creates, controls and profiles all CMSDK threads.
bool createThread(THREAD_FUNCTION func, void *args, uint32 &newID, uint32 reqID)
Instance-side worker for CreateThread(); requires and manages the mutex internally.
bool resizeThreadStorage(uint32 newCount)
Grow the storage block to hold newCount slots.
bool addLocalThreadStats()
Instance-side worker for AddLocalThreadStats() (pthreads self-reporting).
static bool CreateThreadManager()
Create and initialise the singleton (including its monitoring thread).
bool pauseThread(uint32 id)
Instance-side worker for PauseThread().
bool getLocalThreadID(uint32 &id)
Instance-side worker for GetLocalThreadID(): linear scan of slots for the calling thread's OS ID.
static bool GetLocalThreadID(uint32 &id)
Look up the manager slot ID of the calling thread.
bool continueThread(uint32 id)
Instance-side worker for ContinueThread().
ThreadManager()
Construct the manager and pre-allocate storage for 1024 thread slots. Prefer CreateThreadManager().
bool interruptThread(uint32 id)
Instance-side worker for InterruptThread().
ThreadStats * getAllThreadStats(uint32 &count)
Instance-side worker for GetAllThreadStats(): allocates and fills a snapshot array (caller frees with...
static bool InterruptThread(uint32 id)
Kill the thread and restart it from its original entry function.
~ThreadManager()
Destructor: shuts down all threads, frees the storage block and clears the singleton pointer.
bool shutdown()
Stop the monitoring loop and forcibly terminate all remaining threads.
utils::Mutex mutex
Single lock serialising all registry and statistics access.
static ThreadStats GetLocalThreadStats()
Get a copy of the statistics record for the calling thread.
bool terminateThread(uint32 id)
Instance-side worker for TerminateThread().
static ThreadStats GetThreadStats(uint32 id)
Get a copy of the statistics record for a specific thread.
static bool CreateThread(THREAD_FUNCTION func, void *args, uint32 &newID, uint32 reqID=0)
Create a new native thread and start it immediately.
int32 threadMonitoring()
Body of the monitoring thread (slot 0).
unsigned char * data
Contiguous storage block: ThreadDataHeader + bitfield + ThreadStats[].
ThreadStats getThreadStats(uint32 id)
Instance-side worker for GetThreadStats(): copies the slot under the mutex.
static ThreadManager * Singleton
The process-wide instance; NULL until CreateThreadManager() (or any lazy static call) runs.
static bool UnitTest()
Self-contained unit test (create/run/join worker threads, verify stats).
static bool IsThreadRunning(uint32 id)
Check whether the thread is still alive at the OS level.
bool init()
Second-phase init: registers this instance as the singleton and starts the monitoring thread in slot ...
static bool TerminateThread(uint32 id)
Forcibly terminate the thread and release its slot.
static bool AddLocalThreadStats()
Sample and record CPU statistics for the calling thread itself.
static bool Shutdown()
Terminate all managed threads, then destroy the singleton.
static ThreadStats * GetAllThreadStats(uint32 &count)
Snapshot the statistics of all live threads.
static bool PauseThread(uint32 id)
Suspend the thread at whatever point it is currently executing.
static bool ContinueThread(uint32 id)
Resume a thread previously suspended with PauseThread().
bool isThreadRunning(uint32 id)
Instance-side worker for IsThreadRunning().
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
Recursive mutual-exclusion lock, optionally named for cross-process use.
Definition Utils.h:463
bool leave()
Release the mutex.
Definition Utils.cpp:1194
bool enter()
Block until the mutex is acquired.
Definition Utils.cpp:1059
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
Definition PsyTime.cpp:69
std::string PrintTimeDifString(uint64 t, bool us=true, bool ms=true)
Definition PsyTime.cpp:722
int64 GetTimeAge(uint64 t)
Age of a timestamp relative to now.
Definition PsyTime.cpp:25
static THREAD_RET THREAD_FUNCTION_CALL ThreadMonitoring(void *arg)
Entry function of the monitoring thread; delegates to ThreadManager::threadMonitoring().
#define GETTHREADSTATS(data, id)
Compute the address of thread slot id inside storage block data.
uint32 GetThreadStatColAbility()
Report this platform's capability for per-thread CPU statistics collection.
Definition Utils.cpp:3119
bool Sleep(uint32 ms)
Suspend the calling thread.
Definition Utils.cpp:2802
bool PauseThread(ThreadHandle hThread)
Suspend a thread's execution.
Definition Utils.cpp:3023
bool IsThreadRunning(ThreadHandle hThread)
Check whether a thread is still alive.
Definition Utils.cpp:3093
bool CreateThread(THREAD_FUNCTION func, void *args, ThreadHandle &thread, uint32 &osID)
Start a new OS thread.
Definition Utils.cpp:2868
bool TerminateThread(ThreadHandle hThread)
Forcibly kill a thread.
Definition Utils.cpp:2993
bool GetCurrentThreadUniqueID(uint32 &tid)
Get a process-unique id for the calling thread.
Definition Utils.cpp:3043
bool CheckForThreadFinished(ThreadHandle hThread)
Non-blocking check whether a thread has terminated.
Definition Utils.cpp:2914
bool ContinueThread(ThreadHandle hThread)
Resume a thread paused with PauseThread().
Definition Utils.cpp:3032
bool GetFirstFreeBitLoc(const char *bitfield, uint32 bytesize, uint32 &loc)
Find the first 0 (free) bit.
Definition Utils.cpp:2445
bool GetCPUTicks(ThreadHandle hThread, uint64 &ticks)
Get accumulated CPU time of a specific thread.
Definition Utils.cpp:3131
bool SetBit(uint32 loc, bit value, char *bitfield, uint32 bytesize)
Set bit loc to value.
Definition Utils.cpp:2569
uint32 Calc32BitFieldSize(uint32 bitsize)
Compute the byte size needed for a bitfield of bitsize bits, rounded up to a 32-bit boundary.
Definition Utils.cpp:2435
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
void Register_ThreadManager_Tests()
Header at the start of the ThreadManager's contiguous storage block.
uint32 size
Total size of the storage block in bytes.
uint32 statColPolicy
Statistics collection policy (THREAD_STATS_OFF / ADHOC / central), from utils::GetThreadStatColAbilit...
uint32 count
Capacity: number of ThreadStats slots allocated.
uint32 bitFieldSize
Size in bytes of the slot-occupancy bitfield that follows this header.
uint32 activeCount
Number of slots currently holding a live (non-terminated) thread.
Bookkeeping record for a single managed thread.
uint32 status
Lifecycle state (THREAD_NONE/INIT/RUNNING/PAUSED/INTERRUPTED/TERMINATED).
ThreadHandle hThread
Native handle (HANDLE on Windows, pthread_t wrapper on POSIX).
void * args
Argument passed to func, retained for restart.
uint64 currentCPUTicks[10]
Rolling CPU-tick readings matching time (newest first).
uint64 time[10]
Rolling timestamps of the last 10 statistics samples (newest first).
uint32 id
ThreadManager-assigned slot ID (index into the stats array).
uint32 osID
Operating-system thread ID (as reported by the OS, not the slot ID).
THREAD_FUNCTION func
Entry function, retained so InterruptThread() can restart the thread.
uint64 created
Creation time (microseconds, GetTimeNow()); 0 = slot unused/invalid.