Skip to main content
Graduate II
March 7, 2025
Question

Atomic variable for scheduler question

  • March 7, 2025
  • 17 replies
  • 4035 views

I traditionally use a volatile type for my schedulers. Very simple like so

 

volatile uint8_t myflag;
uint8_t ticks;
void Func(void){
 ++ticks;
 if(!(ticks % 5)){
 myflag = true;
 }
}

void User(void){
 if(myflag) {
 myflag = false;
 //DO Something
 }
}

I'm trying to update my knowledge. The online thought seems to be that this isn't really the safe way as this does not have any memory barrier. So the proper way is to use an atomic. So I tried an atomic.

atomic_bool myflag = ATOMIC_VAR_INIT(false);
uint8_t ticks;
void Func(void){
 ++ticks;
 if(!(ticks % 5)){
 atomic_store(&myflag ,true);
 }
}

void User(void){
 if(atomic_load(&myflag)) {
 atomic_store(&myflag ,false);
 //DO Something
 }
}

However, this definitely does not work. The User misses flag settings regularly. Its not just a delay. But per the scope its like a complete flag setting is missed. What have I done wrong here? I even tried to make it volatile and also tried atomic_flag but they both miss settings but the old school way does not miss any settings.

    This topic has been closed for replies.

    17 replies

    Graduate
    March 10, 2025

    Right, but 1. this is not related to "atomicity" and 2. does not apply to physically single-core and single-threaded microcontroller.

    With a single-core, single-threaded microcontroller, you would encounter the problem if a variable of a numeric type was incremented/decremented by two or more pieces of code in two or more threads or a thread and ISR. AFAIK, this is not the case we are discussing here.

    Carl_GAuthor
    Graduate II
    March 10, 2025

    I think you are taking this term Atomicity to only refer to corrupting the value but there is more to it than that.

    If you look at the standard concurrency example of two threads incrementing a number there are 2 issues.

    1. A non-atomic sized number can get torn into a nonsense number. e.g. a 16-bit number incrementing on an 8-bit MCU.

    2. Out of sequence increment causing increments to be lost/overwritten.

    For this 1bit scenario the concern is only #2. Values being lost. As soon as you introduce IRQs you create this problem. Even on single core single threaded mcu.

    Super User
    March 10, 2025

    If you only set the flag (volatile bool) in one thread and only check/reset it in the other thread, which is what you do in your example, you do not have this issue. With incrementing, yes it can be an issue but it depends on the details here. If only one thread modifies a regular sized integer, you don't have an issue.

    If you want a memory barrier, you can do a DSB instruction. However, unless you are also modifying other data that needs to be set before the flag is processed, this is not necessary.

     

    For embedded platforms like the STM32, the "volatile" flag is sufficient to ensure the data is read/written every time it is encountered.

    Graduate
    March 10, 2025

    Right, BUT NOT FOR BOOLEAN SET & RESET.

    In ARM-M architecture the problem appears when you modify a 64-bit number.

    It also appears when any-size the value is modified (arithmetic or logic operation) by low priority code and tested by high priority code.

    It could appear during boolean operations on memory variables - and/or/xor/not, cause they are not atomic at hardware level.

    Carl_GAuthor
    Graduate II
    March 10, 2025

    True for #1 but what about issue #2?

    Carl_GAuthor
    Graduate II
    March 10, 2025

    @TDK you mean if I was updating a separate variable then setting the flag to let another "thread" know the variable was updated then just making the bool volatile would in theory not be enough?

    Super User
    March 10, 2025

    Correct. There are few issues.

    • The compiler may optimize away subsequent accesses. You can prevent this by making it volatile.
    • If modified by DMA, the cpu may have the value cached. You can invalidate the cache to prevent this.
    • (technically) The memory operations are not necessarily executed sequentially. A DSB instruction can make all previous data operations occur before the DSB and all subsequent data operation occur after it. In practice, this is rarely needed.
    Graduate
    March 10, 2025

    Not sure what you mean by issue #2. I believe I addressed it above -> modification based on current value, like increment/decrement/whatever but not assignment like x = 5 or flag = true. When you use constant value assignment, it's atomic at hardware level - the value is only written, not read, so there is no "sequence".

    Carl_GAuthor
    Graduate II
    March 10, 2025

    If you don't know what I mean by #2 you need to reread my post. Its the only relevant point. You are fixated on torn values. I keep explaining that is not the only issue with concurrency. If you find any concurrency example online it will probably include the counting example where numbers are incorrectly skipped. You are consistently ignoring this.

    Graduate
    March 10, 2025

    ... and none of the above (other than volatile) applies to a boolean flag being set by ISR and reset by some other code. :)

    Carl_GAuthor
    Graduate II
    March 10, 2025

    I can't disagree with that :thumbs_up:

    Super User
    March 10, 2025

    The stdatomic API has memory barrier semantics, even if access to one byte is atomic by itself. By default it uses the strongest memory barrier (CST). This can generate MB instructions (overhead in execution time and code size). Otherwise, for CM0 (which has no D cache) the behavior *should* not differ. But you see it differs.

     

    Carl_GAuthor
    Graduate II
    March 13, 2025

    Yes. Volatile variable and atomic variable (which should have memory barriers) should bevahe the same here. They do not.