2

I'm feeling a bit overwhelmed when using multiple threads in embedded programming since each and every shared resource ends up with a getter/setter protected by a mutex.

I would really like to understand if a getter of the following sort

static float static_raw;
float get_raw() {
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    float local_raw = static_raw;
    os_mutex_put(mutex);

    return local_raw ;
}

makes sense or if float assignement can be considered atomic e.g. for ARM (differently from e.g 64bit variables) making this superfluous.

I can understand something like this:

raw = raw > VALUE ? raw + compensation() : raw;

where the value is handled multiple times, but what about when reading or returning it?

Can you make my mind clear?

EDIT 1: Regarding the second question below. let's assume we have an "heavy" function in terms of time execution let's call it

void foo(int a, int b, int c)

where a,b,c are potentially values from shared resources. When the foo function is called should it be enveloped by a mutex, locking it for plenty of time even if it just needs a copy of the value? e.g.

os_mutex_get(mutex, OS_WAIT_FOREVER);
foo(a,b,c);
os_mutex_put(mutex);

does it make any sense to do

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(la,lb,lc);

locking only the copy of the variable instead of the full execution?

EDIT2: Given there could exist getter and setter for "a", "b" and "c". Is it problematic in terms of performance/or anything else in doing something like this?

int static_a;
int get_a(int* la){
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    *la = static_a;
    os_mutex_put(mutex);
}

or

int static_b;
int get_b(){
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    int lb = static_b;
    os_mutex_put(mutex);
    return lb;
}

using them as

void main(){
   int la = 0;
   get_a(&la);
   foo(la,get_b());
}

I'm asking this because im locking and relocking on the same mutex sequentially for potential no reason.

Luigi
  • 376
  • 3
  • 16

2 Answers2

1

The C standard does not dictate anything about the atomicity of the assignment operator. You cannot consider an assignment atomic, as it is completely implementation dependent.

However, in C11 the _Atomic type qualifier (C11 §6.7.3, page 121 here) can be used (if supported by your compiler) to declare variables to be atomically read and written, so you could for example do the following:

static _Atomic float static_raw;

float get_raw(void) {
    return static_raw;
}

Don't forget to compile with -std=c11 if you do so.


Addressing your first edit:

When the foo function is called should it be enveloped by a mutex, locking it for plenty of time even if it just needs a copy of the value?

While it would be correct it certainly would not be the best solution. If the funcion only needs a copy of the variables then your second snippet is without a doubt much better, and should be the ideal solution:

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(la,lb,lc);

If you lock the whole function, you'll block any other thread trying to acquire the lock for much longer than needed, slowing down everything. Locking before calling the function and passing copies of the values will instead only lock for the needed amount of time leaving much more free time to other threads.


Addressing your second edit:

Given there could exist getter and setter for "a", "b" and "c". Is it problematic in terms of performance/or anything else in doing something like this?

That code is correct. In terms of performance it would certainly be much better to have one mutex per variable, if you can. With only one mutex, any thread holding the mutex will "block" any other thread that is trying to lock it, even if they are trying to access a different variable.

If you cannot use multiple mutexes then it's a matter of chosing between these two options:

  1. Lock inside the getters:

    void get_a(int* la){
        os_mutex_get(mutex, OS_WAIT_FOREVER);
        *la = static_a;
        os_mutex_put(mutex);
     }
    
    void get_b(int* lb){
        os_mutex_get(mutex, OS_WAIT_FOREVER);
        *lb = static_b;
        os_mutex_put(mutex);
     }
    
    /* ... */
    
    int var1, var2;
    get_a(&var1);
    get_b(&var2);
    
  2. Lock outside the getters (leave the duty to the caller):

    int get_a(void){
        return static_a;
     }
    
    int get_b(void){
        return static_b;
     }
    
    /* ... */
    
    os_mutex_get(mutex, OS_WAIT_FOREVER);
    int var1 = get_a();
    int var2 = get_b();
    os_mutex_put(mutex);
    

    At this point you wouldn't even need to have getters and you could just do:

    os_mutex_get(mutex, OS_WAIT_FOREVER);
    int var1 = a;
    int var2 = b;
    os_mutex_put(mutex);
    

If your code frequently requests multiple values, then locking/unlocking outside the getters is better since it will cause less overhead. As an alternative, you could also keep the locking inside, but create different functions to retrieve multiple variables so that the mutex is only locked and released once.

On the other hand, if your code is only rarely requesting multiple values, then it's ok to keep the locking inside each getter.

It isn't possible to say what's the best solution ahead of time, you should run different tests and see what's best for your scenario.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • I got you remarks, but assuming i cant use -std=c11 am i doing it right? – Luigi Feb 04 '20 at 18:54
  • @Luigi well yes, if you cannot use `_Atomic` there is no other way than doing it manually like you are doing. – Marco Bonelli Feb 04 '20 at 19:08
  • So also a call like this foo(a,b,c) should be protected by a mutex only because a,b,c can be modified from another thread? Even if i'm passing them by copy? – Luigi Feb 04 '20 at 19:22
  • @Luigi err, it's unclear what you mean here, I don't know anything about foo, a, b, c from your comment. I am only talking about the code you put in your question. – Marco Bonelli Feb 04 '20 at 19:28
  • see the two EDITs in the question please – Luigi Feb 06 '20 at 08:53
1

if float assignement can be considered atomic

Nothing can be considered atomic in C unless you use C11 _Atomic or inline assembler. The underlying hardware is irrelevant, because even if a word of a certain size can be read in a single instruction on the given hardware, there is never a guarantee that a certain C instruction will only result in a single instruction.

does it make any sense to do

os_mutex_get(mutex, OS_WAIT_FOREVER);
int la = a;
int lb = b;
int lc = c;
os_mutex_put(mutex);
foo(a,b,c);

Assuming you mean foo(la,lb,lc);, then yes it makes lots of sense. This is how you should ideally use mutex: minimize the code between mutex locks so that it is just raw variable copying and nothing else.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • can you look at the EDIT 2 as well? – Luigi Feb 06 '20 at 08:52
  • @Luigi Grabbing a mutex always affects performance. You can improve it by placing all related variables sharing the same mutex inside a struct, then provide both `get_a` and `get_abc` getters, where `get_abc` returns a struct containing all 3, but only grabs the mutex once. – Lundin Feb 06 '20 at 09:15