0

I want to be able to modify a non-atomic variable in interrupt context or application context without ever blocking. To prevent the ISR from blocking, I use an atomic flag to guard the non-atomic data.

#include <atomic>
#include <cstdint>

using CountLock = std::atomic<bool>;
static_assert(CountLock::is_always_lock_free, "count lock must be lock free");

#define TARGET_COUNT (1000)
CountLock count_locked{false};
uint64_t  count{0}; // non-atomic on 32-bit system

// In interrupt
void MyISR()
{
    // Don't modify count if locked
    if (count_locked)
        return;

    ++count; // non-atomic

    if (count == TARGET_COUNT)
        // Do something
}

// Application code (not in interrupt)
void setCount(uint64_t newCount)
{
    // lock count while we are setting it
    count_locked = true;
    count = newCount;
    count_locked = false;
}
  1. Does this correctly guard the data?
  2. Is the compiler allowed to compile out the count_locked variable or reorder instructions such that the data is outside the lock?
  3. Am I correct in thinking nothing needs to be marked volatile?
  4. Is this equivalent to a mutex?
tderensis
  • 90
  • 5
  • 1
    What's to stop `count_locked` from becoming true after the evaluation of `if(count_locked)` but before the execution of `++count;`? This doesn't look like it protects `count` at all. – Nathan Pierson Jul 28 '21 at 21:20
  • 2
    If you want to make it so that attempts to modify the critical section are either successful or fail without blocking the attempting thread, you can [use unique_lock with try_lock](https://stackoverflow.com/questions/33980860/use-stdlock-guard-with-try-lock) on a mutex. – Nathan Pierson Jul 28 '21 at 21:31
  • 1
    Given that 64 bit operations are not atomic on 32 bit, interrupt or main code might update half the variable before the other code read it... Thus it multiplies the ways it can go wrong. – Phil1970 Jul 28 '21 at 22:55

1 Answers1

2

Consider two threads concurrently calling MyISR and setCount respectively:

T1                             T2

if (count_locked)
        return;

                               count_locked = true;

 ++count; /*non-atomic*/       count = newCount;        // << data race

 if (count == TARGET_COUNT)
    // Do something

                               count_locked = false;

The atomic does not protect writes to count.

  1. No

  2. The data is modifed without being properly locked anyhow

  3. Yes. Quoting from When to use volatile with multi threading?: "volatile is (nearly) useless for platform-agnostic, multithreaded application programming."

  4. No

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 1
    This part in the linked answer makes me wonder if interrupt routines aren't among those use cases? "_`volatile` was specifically intended to be used when interfacing with memory-mapped hardware, signal handlers, and the `setjmp` machine code instruction. This makes `volatile` directly applicable to systems-level programming rather than normal applications-level programming._" and "_Do not use `volatile` except in low-level code that deals directly with hardware._" - and I wonder if interrupts aren't counted as such? – Ted Lyngmo Jul 28 '21 at 21:32
  • @TedLyngmo next paragraph "The 2003 C++ Standard does not say that volatile applies any kind of Acquire or Release semantics on variables." and for C++11 " However the semantics of volatile still have not changed. volatile is still not a synchronization mechanism. " MSVC seems to give some guarantees. Though imho "no" is ok for "needs to be marked volatile?" – 463035818_is_not_an_ai Jul 28 '21 at 21:39
  • @TedLyngmo err I meant "Yes" is ok for "nothing *needs* to be marked volatile?" – 463035818_is_not_an_ai Jul 28 '21 at 21:40
  • Yeah, I'm (perhaps wrongly) assuming lock free `volatile`s and that a modification of such a variable in an interrupt routine would be read properly in the normal code only if marked `volatile` - as if "hardware may have messed with it - better read it properly". – Ted Lyngmo Jul 28 '21 at 21:42
  • To be clear the setCount() function is only called at application level and never be called when the MyISR() function is being called. Only the MyISR() function can be called when setCount() is being run. – tderensis Jul 29 '21 at 13:34
  • 1
    @tderensis it does not matter which function is called first when there is no synchronization inside the functions. You can still run into the case that `if (count_locked)` is checked before `count_locked = true;` even if `setCount` is called first – 463035818_is_not_an_ai Jul 29 '21 at 13:36