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
============
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
it counts up by the amount of work it did and then it rests.
Every thread will do some work (busy waiting).
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
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
- `bad_wait`: take breaks by busy waiting and therefore hogging the CPU
- `yield_wait`: take no explicit breaks but yields (to higher or equal priority threads)
- `no_wait`: never take a break
- `bad_wait`: takes breaks by busy waiting and therefore hogging the CPU
- `yield_wait`: takes no explicit breaks but yields (to higher or equal priority threads)
- `no_wait`: never takes a break
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)
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
(or the first one to get it).
In this example Round Robin scheduling is enabled by default,
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.:
```
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.
thread 2 will do 2 work bad_wait (hog the cpu while waiting).
Will set:
- 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 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)
thread 3 will never have done work.
The result will be:
- 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 WORK_SCALE 1000
#define STEPS_PER_SET 10
__attribute__((unused))
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);
}__attribute__((unused))
}
static void (* const do_work)(uint32_t us) = bad_wait;
__attribute__((unused))
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);
}__attribute__((unused))
}
__attribute__((unused))
static void yield_wait(uint32_t unused)
{
(void) unused;
/* do not wait just yield */
thread_yield();
}__attribute__((unused))
}
__attribute__((unused))
static void no_wait(uint32_t unused)
{
(void) unused;
/* 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
@ -52,17 +65,12 @@ static void no_wait(uint32_t unused)
* the ration of active (doing hard work like checking the timer)
* to passive (wait to be informed when a certain time is there) waiting
* 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)
{
nice_wait(200 * US_PER_MS); /*always be nice at start*/
nice_wait(200 * US_PER_MS); /* always be nice at start */
#ifdef DEVELHELP
const char *name = thread_get_active()->name;
#else
@ -71,17 +79,18 @@ void * thread_worker(void * d)
uint32_t w = 0;
struct worker_config *wc = d;
/* this is doing 10 Steps which divided into work (busy waiting)
* and not work of these 10 steps up to 10 might be work but not more
* if the given value is out of range work ratio is set to 5 */
uint32_t work = wc->work;
if (work > 10) {
work = 5;
/* Each set consists of STEPS_PER_SET steps which are divided into work (busy waiting)
* and resting.
* E.g. if there are 10 steps per set, the maximum workload is 10, which means no rest.
* If the given value is out of range work ratio is set to half of STEPS_PER_SET */
uint32_t work = wc->workload;
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;
/*work some time and rest*/
/* work some time and rest */
for (;;) {
if (w - step >= PRINT_STEPS) {
#ifdef DEVELHELP
@ -91,18 +100,18 @@ void * thread_worker(void * d)
#endif
step = w;
}
bad_wait(work * WORK_SCALE);
do_work(work * WORK_SCALE);
w += work;
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
* 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
#define THREAD_1 {no_wait, 5}
@ -116,23 +125,28 @@ void * thread_worker(void * d)
#define THREAD_3 {no_wait, 5}
#endif
/*a TINY Stack should be enough*/
#ifndef WORKER_STACKSIZE
#define WORKER_STACKSIZE (THREAD_STACKSIZE_TINY+THREAD_EXTRA_STACKSIZE_PRINTF)
#endif
int main(void)
{
{
static char stack[THREAD_STACKSIZE_DEFAULT];
static struct worker_config wc = THREAD_1; /* 0-10 workness*/
static char stack[WORKER_STACKSIZE];
static struct worker_config wc = THREAD_1; /* 0-10 workness */
thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST,
thread_worker, &wc, "T1");
}
{
static char stack[THREAD_STACKSIZE_DEFAULT];
static struct worker_config wc = THREAD_2; /* 0-10 workness*/
static char stack[WORKER_STACKSIZE];
static struct worker_config wc = THREAD_2; /* 0-10 workness */
thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST,
thread_worker, &wc, "T2");
}
{
static char stack[THREAD_STACKSIZE_DEFAULT];
static struct worker_config wc = THREAD_3; /* 0-10 workness*/
static char stack[WORKER_STACKSIZE];
static struct worker_config wc = THREAD_3; /* 0-10 workness */
thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST,
thread_worker, &wc, "T3");
}