Using for or while loops for embedded code

Hi,

I know that for and while loops are blocking functions.

  1. Does that mean they aren't favored in embedded applications especially for critical and professional coding ?

  2. Using small loops with "for" or "while" in small iterations; like, 5 iterations would ok ?

  3. And for more secure code, is it better to use interrupts incase I used these blocking functions ?

Actually one of my main coding goals is to avoid using for and while loops. For example, I'm working on coroutine project where I want to manage threads as there maybe more than 1 thread running, so it has to run in multitasking system in order to mange multiple threads.

And to achieve that I have to avoid using blocking code.

No. Not if they have predictable exit conditions. As in your question 2.

The comments on your coding goals are strange - there isn't really any relationship between multi-threading and the use of for and while loops.

1 Like

for-loops and while-loops are not really blocking. They will be if you e.g. increment a counter 1,000,000,000 times.

  1. Not necessarily; if you do something in a for-loop with delays (and possibly printing), yes it will be blocking.
  2. See (1)
  3. I would say, it depends. ISRs should be fast; usually you will set a flag and in loop() check the flag; and if loop() contains blocking functions, you will still be late to react on the flag.

// Edit: And in the Arduino world, loop() is called from a for-loop :wink:

1 Like

Yes, and much anguish and suffering often happens when someone realizes #3, and decides they have to do all the processing inside the interrrupt, which doesn't work because interrupts are disabled during that execution.

Can you provide me with some examples of how to manage multithreading code that dispatch each thread tasks in a for or while loops ?

I'm not using any delays in my current codes.

I'm working on 2 multitasking projects:

  1. multithreading with void pointers
  2. multithreading with void coroutines

Both are working, here is how I mange the one with void pointers:

void run_thread(THREAD *thrd, TASK *tsk){
	if(thrd->thrd_st == NOT_FINISHED){									// check if thread finished all tasks
		if((thrd->tsk_cntr) < (thrd->tsk_cnts)){						// check linear task execution
			if(*((STATE*)thrd->tsk_st) == NOT_FINISHED){				// check this task is NOT_FINISHED
				switch((tsk+thrd->tsk_cntr)->args_cnts){				// run task by args_cnts
					case 0:	// dereferencing 0 args
						((void(*)())(tsk+thrd->tsk_cntr)->tsk_fptr)();
					break;

					case 1: // dereferencing 1 args
						((void(*)(uint8_t*))(tsk+thrd->tsk_cntr)->tsk_fptr)(
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+0);
					break;

					case 2: // dereferencing 2 args
						((void(*)(uint8_t*,uint8_t*))(tsk+thrd->tsk_cntr)->tsk_fptr)(
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+0,
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+1);
					break;
					
					case 3: // dereferencing 3 args
						((void(*)(uint8_t*,uint8_t*,uint8_t*))
						(tsk+thrd->tsk_cntr)->tsk_fptr)(
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+0,
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+1,
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+2);
					break;

					case 4: // dereferencing 4 args
						((void(*)(uint8_t*,uint8_t*,uint8_t*,uint8_t*))
						(tsk+thrd->tsk_cntr)->tsk_fptr)(
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+0,
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+1,
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+2,
						(uint8_t*)(tsk+thrd->tsk_cntr)->tsk_args+3);
					break;
				}
			}
			else{											// if current task is finished
				
				thrd->tsk_cntr++;							// go to next task
				*((STATE*)thrd->tsk_st) = NOT_FINISHED;		// reset finish st flag	
			}
		}
		else{			
			thrd->thrd_st = FINISHED;
		}
	}
}

Here's my task manager with coroutines that one told me about and provided me with this scheme of running tasks, but the issue with this one is that it use a do {} while(); loop which will block another thread:

void RunCoroutine(uint8_t count, cr_fptr funcs[]){
    bool done;
    uint8_t i;

    bool *state = malloc(sizeof(bool) * count);
    for(i=0;i<count;i++){ state[i] = Yielding; }

    do
    {
        done = true;
        for (i = 0; i < count; i++)
        {
            if (state[i] == Yielding){
                state[i] = funcs[i]();
            }

            done &= state[i] == Done;
        }
    } while (!done);

    free(state);
}

Any suggestions of how to improve any of the functions I posted ?

You should at least check if malloc() succeeded.

1 Like

I can barely read your code without going over budget, but at a glance I must wonder if the C varargs mechanism might simplify your code.

a7

1 Like

For the code using void pointer, it works, here's my test code:

#include "task_manager.h"
#include "glcd_spi.h"
#include "sensors_modules.h"
#include "arrays.h"

extern STATE lcd_st_flag;

void setup() {
  Serial.begin(9600);
  ////////////////////////////// LCD THREAD initialization //////////////////////////////
  // allocate & initialize lcd thread
  THREAD *lcd_thrd = (THREAD*) malloc(1*sizeof(THREAD));
  TASK   *lcd_tsk  = (TASK*)   malloc(4*sizeof(TASK)); // method #1

  // lcd thread initialization
  *lcd_thrd = (THREAD){4, 0, NOT_FINISHED, &lcd_st_flag};
  
  // lcd task initialization
  *(lcd_tsk+0) = (TASK){0, 0, glcd_init};
  *(lcd_tsk+1) = (TASK){1, 1, glcd_graphics_mode};
  *(lcd_tsk+2) = (TASK){0, 0, glcd_clr};
  *(lcd_tsk+3) = (TASK){1, &p2, glcd_img};

  ////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////

  while(lcd_thrd->thrd_st != FINISHED){
    run_thread(lcd_thrd, lcd_tsk);

  }

  Serial.println("thread finished");
  
}

I tested one thread that has 4 tasks and it's working ok. My next step is to do 2 versions of this function.

  1. run tasks in sequential mode
  2. run tasks round robin

Another thing, I don't want to mix things and confuse the people who want to help me.

I now posted 2 different projects of multitasking.

  1. using void pointers
  2. using coroutines

I want to keep working on the 2nd one because it has the for loop issue I want to solve.

I also would like to leave the one with void pointers for later post, when I get to a point which has an issue and I can't solve it.

I have thought about it, but I still don't find it easy to use varags.

Check this link:
https://locklessinc.com/articles/overloading/

I have tried this week to search for a way to process different functions with different variables, but I didn't find what I want.

Until now the code using void pointers is the best thing I've done until now to process the functions in my projects.

Of course varargs would offer many number of arguments to pass to a function. But maybe I would try to give it another shot.

In embedded applications for loops are typically used when the number of iterations is known at compile time. This makes them predictable, and you decide how many iterations you want, how much time each loop takes and therefore the overall time. If the time is short enough to allow other tasks to run as often as they need you will be fine using for loops.

While loops on the other hand are often used when the number of iterations is not known or when the sketch waits for some event to happen (e.g., some flag set by hardware). This makes them less predictable especially for beginners and therefore I recommend to beginners to avoid them. That does not mean they should never be used.

Interrupt service routines should be as short as possible. They should not be used to solve any complex problem. With new microcontrollers you can use smart infrastructure to make peripherals work together reducing the number of interrupts or processor cycles needed to solve a task. This is less portable from one micro to another but allows some part of your application to run almost or entirely independent of software.

1 Like

while real time performance is relative (1 sec, 1 msec, 1 usec), code needs to execute in a predictable amount of time.

of course for/while loops can be used. however, if they take too much time, the number of iterations needs to be limited and processing distributed over a number of cycles.

one approach might be to only perform some number of iterations within some time period.

depends on the requirements

1 Like

Yes, I understand your replies and I know this information about why and where to use a for or while loops + I learned other ways to use for loop without incrementing and use it for a start value and a condition; like this one:

for(st_millis = millis(); millis() - st_millis < prd; ) {
    ...   
}

And it works fine for coroutine; this is the version I found online and implement it in my coroutine library:

#define                                                 \
    CO_BEGIN                                      		\
    static int state = 0;                               \
    switch(state)                                       \
    {                                                   \
        case 0:
#define 								                \
    co_delay(prd)									    \
    do												    \
    {												    \
        static unsigned long st_millis = 0;			    \
        for(st_millis = millis();                       \
            millis() - st_millis < prd;)                \
        {                                               \
                state = __LINE__;			            \
                return Yielding;                        \
                case __LINE__:			                \
                ;           			                \
        }			                                    \
    }			                                        \
    while (0)

#define                                                 \
    CO_END                                        		\
    }                                                   \
    state = 0; 					            			\
    return Done

So, the for loop is terminated every time the condition in the for argument is false. Then it proceeds to the next line.

I liked coroutines for these macros, works like the blocking delay function.


To not go away of my main issue of this post which is solved in the coroutine macros. But the issue is actually in the coroutine task_manager which use a for loop to move between tasks.

Where the coroutine_task_manager is blocking to other threads which is my exact issue.

So what's the solution in this situation ? How to run multiple coroutine threads without blocking each other ?

void RunCoroutine(uint8_t count, cr_fptr funcs[]){
    bool done;
    uint8_t i;

    bool *state = malloc(sizeof(bool) * count);
    for(i=0;i<count;i++){ state[i] = Yielding; }

    do
    {
        done = true;
        for (i = 0; i < count; i++)
        {
            if (state[i] == Yielding){
                state[i] = funcs[i]();
            }

            done &= state[i] == Done;
        }
    } while (!done);

    free(state);
}

I thought of using if statement; here:

void run_coroutine_linear(uint8_t count, cr_fptr cr_tasks){
    // lock this part for only initializing the function process data
	if(!l_lock){
		l_cnt = 0;									// tasks counter
		l_cr_st = 0;								// coroutine state
		*l_tsk_st = malloc(sizeof(bool) * count);	// allocate dynamic array for tasks states
		
		// initialize all tasks to be Yielding
		uint8_t i = 0;
		for(i=0; i<count; i++){l_tsk_st[i] = Yielding;}
		l_lock = 1;									// lock this part
	}

	if(l_cnt < count){
		if(l_tsk_st[l_cnt] == Yielding){
			// <------------------------------------------------------------
			// the problem here, which is how to process different functions
			// with multiple arguments number/type
		}
		else{
			l_tsk_st[l_cnt] = Done;
			l_cnt++;
		}
	}
    else{
		// if number of tasks is finished, set coroutine state as done
        l_cr_st = 1;
    }	
}

I did some parts in this function but the problem is the same in the one using void pointers.

What you suggest for me ? I signed the issue part with an arrow.

on an arduino?

wouldn't a multi-tasking OS handle that using pre-emption?

1 Like

Is this true multithreading with context switching where tasks can yield and resume later, or where the runtime can preempt running tasks? At first sight, your code has only one thread of execution, and all functions run to completion. In this case, I think “cooperative multitasking” would be a more suitable name than “multithreading”.

Void pointers are a cancer that spread through your code base, I'd highly recommend designing some sort of type erasure abstraction to manage different argument types and counts. You could get some inspiration from how std::function is implemented, for example.

C++20 has language support for coroutines, there's no need to implement this in user code, which is much harder than using the conveniences provided by the compiler.
If you really want to reinvent the wheel yourself, have a look at Boost::asio for inspiration.

That's where you would use co_await rather than looping. Blocking loops do not mix with coroutines, the whole point is that coroutines can be resumed later, without actually waiting.
Contrary to what you might infer from the name, await suspends the coroutine and schedules it to be resumed later, it doesn't actually wait.

1 Like

Yes, but I haven't done any pre-emption code.
But it should be one of my future projects.

Now I want to master the normal multitasking with series/parallel tasks executions.

Is this true multithreading with context switching where tasks can yield and resume later, or where the runtime can preempt running tasks? At first sight, your code has only one thread of execution, and all functions run to completion. In this case, I think “cooperative multitasking” would be a more suitable name than “multithreading”.

  1. No, it doesn't have context switching.
  2. It has one thread as an example, I can put another thread with its tasks.
  3. Yes, it's a "cooperative multitasking", but with a feature of grouping multiple tasks to a certain thread and monitor that thread in the manager function.

Void pointers are a cancer that spread through your code base,

Yes, this was the only way I could achieve this code using void pointers because it allows me to pass whatever arguments type I want with casting everything.

I'd highly recommend designing some sort of type erasure abstraction to manage different argument types and counts.

How to do that in C ?

==========================================================

I tried to find a better way in the first time, but I found that void pointers are the only solution in C, because I think that I can do almost but not entirely anything with C.

I really want to learn as much as I can in C before using C++, but I read C++ libraries from a time to time and I know some of the methods that C++ use.
I'm not a fast learner that's why I'm taking a lot of time that can be from months to years to cover some skills in C.

C++20 has language support for coroutines, there's no need to implement this in user code, which is much harder than using the conveniences provided by the compiler.

Where to find this support for coroutines ?

If you really want to reinvent the wheel yourself, have a look at Boost::asio for inspiration.

No, I'm not trying to invent the wheel, it's not my main goal. I just want a task_manager that is easy to use for me and has the features I want.

I downloaded many multitasking libraries and I didn't like them very much, I prefer to do my own version even if it's almost identical to another library, I copy functions sometimes and also I copy methods and ideas of the library I consider to follow its main scheme.

That's where you would use co_await rather than looping. Blocking loops do not mix with coroutines, the whole point is that coroutines can be resumed later, without actually waiting.
Contrary to what you might infer from the name, await suspends the coroutine and schedules it to be resumed later, it doesn't actually wait.

Let me try that on thread level. I'm only using:
CO_BEGIN, co_delay(prd) & CO_END

Maybe I should find away to multitask between multiple coroutine_manager.

what is a "coroutine"?

seems like you're trying to "master" an environment that isn't a multi-tasking operating system, just with a set of function/sub-functions with different purposes

i've written real-time applications on DSPs that don't use an "OS", by just managing I/O and processing around real-time requirements

(a multi-tasking OS is a social management tool -- think about it)

1 Like

From Coroutines (C++20) - cppreference.com :

A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack. This allows for sequential code that executes asynchronously (e.g. to handle non-blocking I/O without explicit callbacks), and also supports algorithms on lazy-computed infinite sequences and other uses.

The rest of the page is rather technical, and the example doesn't really do a good job demonstrating the use cases.

The Wikipedia page has some examples and a comparison with normal functions (subroutines): Coroutine - Wikipedia

1 Like

sounds like it need multi-tasking support

what causes the task/thread to resume execution