1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

examples/thread-duel: improve duelling threads example

This commit is contained in:
Karl Fessel 2021-10-27 14:10:19 +02:00
parent fc3f5f562b
commit 64b783b9fa
3 changed files with 80 additions and 47 deletions

View File

@ -0,0 +1,12 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
nucleo-f031k6 \
nucleo-l011k4 \
samd10-xmini \
stm32f030f4-demo \
#

View File

@ -1,38 +1,45 @@
Thread-Duel Thread-Duel
============ ============
This is a thread duel application to show RIOTs multi-threading abilities This is a thread duel application to show RIOTs abilities to run multiple-threads
concurrently, even if they are neither cooperative nor dividable into different scheduler priorities,
by using the optional round-robin scheduler module.
Every thread will do some work (busy waiting) after the work is done Every thread will do some work (busy waiting).
it counts up by the amount of work it did and then it rests. After the work is done it counts up by the amount of work it did and then it rests.
There are different resting strategies and these have a huge There are different resting strategies and these have a huge
influence on thread fairness and scheduling. influence on thread fairness and scheduling.
resting strategies for the threads of this example are: Resting strategies for the threads of this example are:
- `nice_wait`: does nice breaks giving other threads time to use the CPU - `nice_wait`: does nice breaks giving other threads time to use the CPU
- `bad_wait`: take breaks by busy waiting and therefore hogging the CPU - `bad_wait`: takes breaks by busy waiting and therefore hogging the CPU
- `yield_wait`: take no explicit breaks but yields (to higher or equal priority threads) - `yield_wait`: takes no explicit breaks but yields (to higher or equal priority threads)
- `no_wait`: never take a break - `no_wait`: never takes a break
After completing a batch of work (and rest) a thread will print information on the work done. After completing a batch of work (and rest) a thread will print information on the work done.
(Printing is done in steps to avoid flooding) (Printing is done in steps to avoid flooding)
If one thread (all are same priority) does rests `bad_wait`ing or `no_wait`ing at all, If one thread (all are same priority) follows `bad_wait` or `no_wait` strategy,
scheduling without round robin will see all CPU time be hogged by that one thread scheduling without round robin will see all CPU time be hogged by that one thread
(or the first one to get it). (or the first one to get it).
In this example Round Robin scheduling is enabled by default, In this example Round Robin scheduling is enabled by default,
to disable compile with `RR=0` to disable compile with `RR=0`
change the behaviour of the different thread by adding `CFLAGS='-DTHREAD_1={no_wait,5}'` Change the behaviour of the different threads by adding
`CFLAGS='-DTHREAD_1={<rest_strategy>,<work>}'`
e.g.: e.g.:
``` ```
CFLAGS='-DTHREAD_1={yield_wait,3} -DTHREAD_2={bad_wait,2}' RR=0 make CFLAGS='-DTHREAD_1={yield_wait,3} -DTHREAD_2={bad_wait,2}' RR=0 make
``` ```
will set thread 1 to be yield_waiting and to 3 works in one batch after that it will yield. Will set:
thread 2 will do 2 work bad_wait (hog the cpu while waiting). - thread 1 to follow `yield_waiting` strategy and
to complete 3 works in one batch after that, it will yield.
- thread 2 will do 2 work and follow `bad_wait` (hog the cpu while waiting).
the Round Robin scheduling is not activated. the Round Robin scheduling is not activated.
the result will be a CPU hogged by thread 2 which will only do work for 20% of the time (really bad)
(thread 1 will have done 3 work but will never even get the chance to print that) The result will be:
thread 3 will never have done work. - CPU hogged by thread 2, which will only do work for 20% of the time (really bad)
- thread 1 will have done 3 work but will never even get the chance to print that
- thread 3 will never have done any work.

View File

@ -20,31 +20,44 @@
#define PRINT_STEPS 10 #define PRINT_STEPS 10
#define WORK_SCALE 1000 #define WORK_SCALE 1000
#define STEPS_PER_SET 10
__attribute__((unused))
static void bad_wait(uint32_t us) static void bad_wait(uint32_t us)
{ {
/*keep the CPU busy waiting for some time to pass simulate working*/ /* keep the CPU busy waiting for some time to pass simulate working */
ztimer_spin(ZTIMER_USEC, us); ztimer_spin(ZTIMER_USEC, us);
}__attribute__((unused)) }
static void (* const do_work)(uint32_t us) = bad_wait;
__attribute__((unused))
static void nice_wait(uint32_t us) static void nice_wait(uint32_t us)
{ {
/*be nice give the CPU some time to do other things or rest*/ /* be nice give the CPU some time to do other things or rest */
ztimer_sleep(ZTIMER_USEC, us); ztimer_sleep(ZTIMER_USEC, us);
}__attribute__((unused)) }
__attribute__((unused))
static void yield_wait(uint32_t unused) static void yield_wait(uint32_t unused)
{ {
(void) unused; (void) unused;
/* do not wait just yield */ /* do not wait just yield */
thread_yield(); thread_yield();
}__attribute__((unused)) }
__attribute__((unused))
static void no_wait(uint32_t unused) static void no_wait(uint32_t unused)
{ {
(void) unused; (void) unused;
/* do not wait */ /* do not wait */
}__attribute__((unused)) }
/* worker_config is a small configuration structure for the thread_worker */
struct worker_config {
void (*waitfn)(uint32_t); /**< the resting strategy */
uint32_t workload; /**< the amount of work to do per set */
};
/* /*
* the following are threads that count and wait with different strategies and * the following are threads that count and wait with different strategies and
@ -52,17 +65,12 @@ static void no_wait(uint32_t unused)
* the ration of active (doing hard work like checking the timer) * the ration of active (doing hard work like checking the timer)
* to passive (wait to be informed when a certain time is there) waiting * to passive (wait to be informed when a certain time is there) waiting
* is determined by there value given to the thread. * is determined by there value given to the thread.
* the restless threads do never pause. * no_wait and yield_wait threads are restless an therefore never pause.
*/ */
struct worker_config {
void (*waitfn) (unsigned);
uint32_t work;
};
void * thread_worker(void * d) void * thread_worker(void * d)
{ {
nice_wait(200 * US_PER_MS); /*always be nice at start*/ nice_wait(200 * US_PER_MS); /* always be nice at start */
#ifdef DEVELHELP #ifdef DEVELHELP
const char *name = thread_get_active()->name; const char *name = thread_get_active()->name;
#else #else
@ -71,17 +79,18 @@ void * thread_worker(void * d)
uint32_t w = 0; uint32_t w = 0;
struct worker_config *wc = d; struct worker_config *wc = d;
/* this is doing 10 Steps which divided into work (busy waiting) /* Each set consists of STEPS_PER_SET steps which are divided into work (busy waiting)
* and not work of these 10 steps up to 10 might be work but not more * and resting.
* if the given value is out of range work ratio is set to 5 */ * E.g. if there are 10 steps per set, the maximum workload is 10, which means no rest.
uint32_t work = wc->work; * If the given value is out of range work ratio is set to half of STEPS_PER_SET */
if (work > 10) { uint32_t work = wc->workload;
work = 5; if (work > STEPS_PER_SET) {
work = STEPS_PER_SET / 2;
} }
uint32_t rest = (10 - work); uint32_t rest = (STEPS_PER_SET - work);
uint32_t step = 0; uint32_t step = 0;
/*work some time and rest*/ /* work some time and rest */
for (;;) { for (;;) {
if (w - step >= PRINT_STEPS) { if (w - step >= PRINT_STEPS) {
#ifdef DEVELHELP #ifdef DEVELHELP
@ -91,18 +100,18 @@ void * thread_worker(void * d)
#endif #endif
step = w; step = w;
} }
bad_wait(work * WORK_SCALE); do_work(work * WORK_SCALE);
w += work; w += work;
wc->waitfn(rest * WORK_SCALE); wc->waitfn(rest * WORK_SCALE);
} }
} }
/*
/* no_wait -> a restless thread always working until it is suspended
* yield_wait -> a restless thread that yields before continuing with the next work package
* bad_wait -> a thread that does pause very intensely
* nice_wait -> a thread does nice breaks giving other threads time to do something * nice_wait -> a thread does nice breaks giving other threads time to do something
* bad_wait -> a thread that waits by spinning (intensely looking at the clock)
* yield_wait -> a restless thread that yields before continuing with the next work package
* no_wait -> a restless thread always working until it is preempted
*/ */
/* yield_wait and nice_wait threads are able to work in "parallel" without sched_round_robin*/ /* yield_wait and nice_wait threads are able to work in "parallel" without sched_round_robin */
#ifndef THREAD_1 #ifndef THREAD_1
#define THREAD_1 {no_wait, 5} #define THREAD_1 {no_wait, 5}
@ -116,23 +125,28 @@ void * thread_worker(void * d)
#define THREAD_3 {no_wait, 5} #define THREAD_3 {no_wait, 5}
#endif #endif
/*a TINY Stack should be enough*/
#ifndef WORKER_STACKSIZE
#define WORKER_STACKSIZE (THREAD_STACKSIZE_TINY+THREAD_EXTRA_STACKSIZE_PRINTF)
#endif
int main(void) int main(void)
{ {
{ {
static char stack[THREAD_STACKSIZE_DEFAULT]; static char stack[WORKER_STACKSIZE];
static struct worker_config wc = THREAD_1; /* 0-10 workness*/ static struct worker_config wc = THREAD_1; /* 0-10 workness */
thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST,
thread_worker, &wc, "T1"); thread_worker, &wc, "T1");
} }
{ {
static char stack[THREAD_STACKSIZE_DEFAULT]; static char stack[WORKER_STACKSIZE];
static struct worker_config wc = THREAD_2; /* 0-10 workness*/ static struct worker_config wc = THREAD_2; /* 0-10 workness */
thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST,
thread_worker, &wc, "T2"); thread_worker, &wc, "T2");
} }
{ {
static char stack[THREAD_STACKSIZE_DEFAULT]; static char stack[WORKER_STACKSIZE];
static struct worker_config wc = THREAD_3; /* 0-10 workness*/ static struct worker_config wc = THREAD_3; /* 0-10 workness */
thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST,
thread_worker, &wc, "T3"); thread_worker, &wc, "T3");
} }