cancel
Showing results for 
Search instead for 
Did you mean: 

Atomic variable for scheduler question

Carl_G
Senior

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.

27 REPLIES 27

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.

If you feel a post has answered your question, please click "Accept as Solution".
Carl_G
Senior

@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?

gbm
Principal

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".

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice

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.

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.
If you feel a post has answered your question, please click "Accept as Solution".
gbm
Principal

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

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
Carl_G
Senior

I can't disagree with that :thumbs_up:

Pavel A.
Evangelist III

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.