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:
parent
fc3f5f562b
commit
64b783b9fa
12
examples/thread_duel/Makefile.ci
Normal file
12
examples/thread_duel/Makefile.ci
Normal 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 \
|
||||
#
|
@ -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.
|
||||
|
@ -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");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user