Mark3 Realtime Kernel
lab9_dynamic_threads/main.cpp

This example demonstrates how to create and destroy threads dynamically at runtime.

/*===========================================================================
_____ _____ _____ _____
___| _|__ __|_ |__ __|__ |__ __| __ |__ ______
| \ / | || \ || | || |/ / ||___ |
| \/ | || \ || \ || \ ||___ |
|__/\__/|__|_||__|\__\ __||__|\__\ __||__|\__\ __||______|
|_____| |_____| |_____| |_____|
--[Mark3 Realtime Platform]--------------------------------------------------
Copyright (c) 2012 - 2019 m0slevin, all rights reserved.
See license.txt for more information
===========================================================================*/
#include "mark3.h"
#include "memutil.h"
/*===========================================================================
Lab Example 9: Dynamic Threading
Lessons covered in this example include:
- Creating, pausing, and destorying dynamically-created threads at runtime
Takeaway:
In addition to being able to specify a static set of threads during system
initialization, Mark3 gives the user the ability to create and manipu32ate
threads at runtime. These threads can act as "temporary workers" that can
be activated when needed, without impacting the responsiveness of the rest
of the application.
===========================================================================*/
extern "C" {
void __cxa_pure_virtual(void) {}
void DebugPrint(const char* szString_);
}
namespace
{
using namespace Mark3;
//---------------------------------------------------------------------------
// This block declares the thread data for one main application thread. It
// defines a thread object, stack (in word-array form), and the entry-poi nt
// function used by the application thread.
Thread clApp1Thread;
void App1Main(void* unused_);
//---------------------------------------------------------------------------
// This block declares the thread stack data for a thread that we'll create
// dynamically.
//---------------------------------------------------------------------------
// idle thread -- do nothing
Thread clIdleThread;
void IdleMain(void* /*unused_*/)
{
while (1) {}
}
#define MAX_THREADS (10)
Thread* apclActiveThreads[10];
uint32_t au32ActiveTime[10];
void PrintThreadSlack(void)
{
Kernel::DebugPrint("Stack Slack");
for (uint8_t i = 0; i < MAX_THREADS; i++) {
if (apclActiveThreads[i] != 0) {
char szStr[10];
auto u16Slack = apclActiveThreads[i]->GetStackSlack();
MemUtil::DecimalToString(u16Slack, szStr);
}
}
}
void PrintCPUUsage(void)
{
Kernel::DebugPrint("Cpu usage\n");
for (int i = 0; i < MAX_THREADS; i++) {
if (apclActiveThreads[i] != 0) {
// KernelAware::Trace(0, __LINE__, (K_ADDR)apclActiveThreads[i], au16ActiveTime[i]);
}
}
}
void ThreadCreate(Thread* pclThread_)
{
{
const auto cg = CriticalGuard{};
for (uint8_t i = 0; i < MAX_THREADS; i++) {
if (apclActiveThreads[i] == 0) {
apclActiveThreads[i] = pclThread_;
break;
}
}
}
PrintThreadSlack();
PrintCPUUsage();
}
void ThreadExit(Thread* pclThread_)
{
{
const auto cg = CriticalGuard{};
for (uint8_t i = 0; i < MAX_THREADS; i++) {
if (apclActiveThreads[i] == pclThread_) {
apclActiveThreads[i] = 0;
au32ActiveTime[i] = 0;
break;
}
}
}
PrintThreadSlack();
PrintCPUUsage();
}
void ThreadContextSwitch(Thread* pclThread_)
{
static uint32_t u32LastTicks = 0;
auto u32Ticks = Kernel::GetTicks();
{
const auto cg = CriticalGuard{};
for (uint8_t i = 0; i < MAX_THREADS; i++) {
if (apclActiveThreads[i] == pclThread_) {
au32ActiveTime[i] += u32Ticks - u32LastTicks;
break;
}
}
}
u32LastTicks = u32Ticks;
}
//---------------------------------------------------------------------------
void WorkerMain1(void* arg_)
{
auto* pclSem = static_cast<Semaphore*>(arg_);
uint32_t u32Count = 0;
// Do some work. Post a semaphore to notify the other thread that the
// work has been completed.
while (u32Count < 1000000) { u32Count++; }
Kernel::DebugPrint("Worker1 -- Done Work\n");
pclSem->Post();
// Work is completed, just spin now. Let another thread destory u16.
while (1) {}
}
//---------------------------------------------------------------------------
void WorkerMain2(void* arg_)
{
uint32_t u32Count = 0;
while (u32Count < 1000000) { u32Count++; }
Kernel::DebugPrint("Worker2 -- Done Work\n");
// A dynamic thread can self-terminate as well:
}
//---------------------------------------------------------------------------
void App1Main(void* unused_)
{
Thread clMyThread;
Semaphore clMySem;
clMySem.Init(0, 1);
while (1) {
// Example 1 - create a worker thread at our current priority in order to
// parallelize some work.
clMyThread.Init(awApp2Stack, sizeof(awApp2Stack), 1, WorkerMain1, (void*)&clMySem);
clMyThread.Start();
// Do some work of our own in parallel, while the other thread works on its project.
uint32_t u32Count = 0;
while (u32Count < 100000) { u32Count++; }
Kernel::DebugPrint("Thread -- Done Work\n");
PrintThreadSlack();
// Wait for the other thread to finish its job.
clMySem.Pend();
// Once the thread has signalled u16, we can safely call "Exit" on the thread to
// remove it from scheduling and recycle it later.
clMyThread.Exit();
// Spin the thread up again to do something else in parallel. This time, the thread
// will run completely asynchronously to this thread.
clMyThread.Init(awApp2Stack, sizeof(awApp2Stack), 1, WorkerMain2, 0);
clMyThread.Start();
u32Count = 0;
while (u32Count < 1000000) { u32Count++; }
Kernel::DebugPrint("Thread -- Done Work\n");
// Check that we're sure the worker thread has terminated before we try running the
// test loop again.
while (clMyThread.GetState() != ThreadState::Exit) {}
Kernel::DebugPrint(" Test Done\n");
PrintThreadSlack();
}
}
} // anonymous namespace
using namespace Mark3;
//---------------------------------------------------------------------------
int main(void)
{
// See the annotations in previous labs for details on init.
clIdleThread.Init(awIdleStack, sizeof(awIdleStack), 0, IdleMain, 0);
clIdleThread.Start();
clApp1Thread.Init(awApp1Stack, sizeof(awApp1Stack), 1, App1Main, 0);
clApp1Thread.Start();
return 0;
}