简体   繁体   中英

How to atomically read multiple variable at once in C?

I am trying to read three variables a, b, c atomically at once. The pattern looks something like the code below.

_Atomic uint32_t a, b, c;

void thread_high_priority(void)
{
  atomic_fetch_sub_explicit(&a, 1, memory_order_relaxed);
  atomic_fetch_add_explicit(&b, 1, memory_order_relaxed);
  atomic_fetch_sub_explicit(&c, 1, memory_order_relaxed);
}

void thread_low_priority(void)
{
  uint32_t _a = a;
  uint32_t _b = b;
  uint32_t _c = c;
}

thread_high_priority is a thread running in high priority and thread_low_priority running in low priority. thread_high_priority can interrupt the execution of thread_low_priority , but not the other way. That is, thread_high_priority will always run uninterruptedly.

The constraint is that thread_high_priority is time-critical. Therefore, I don't want to use a mutex to block as it is time-consuming and even causes deadlock. Is there a way to make sure all three variables are read at once without interruption?

Edit: The platform is ARMv7M architecture running in baremetal environment.

You can solve this problem with a level of indirection.

As long as there is only one writer, you could do it like this:

  • Put the set of data items in a struct
  • Allocate several such structs
  • Write non-atomically to the members of a struct which the readers are not using
  • Atomically change a pointer to which struct the reader should use

The reader should read the pointer then access the data in the respective struct.

If it is possible that another interrupt occurs while the main context is still reading then you need to keep a pointer to which struct the reader is using, and the writer can check this before filling out the struct. Accessing this second pointer atomically is easier if there is only one reader.

To smooth things out you can allocate three or more structs, and treat them as a ring buffer.

I also come up with another solution that based on SeqLock. After knowing that what I tried to achieve is essentially tear-detection, I rewrite it using a SeqLock template . I still define my three variables a, b, c as _Atomic uint32_t since I also want to modify them in thread_low_priority using atomic_fetch_* .

On ARMv7-M archiecture RMW atomic operations are implement using ldrex/strex . The compiler will issue a loop to check whether strex success or not. In my case, it could be a problem when using RMW operations because thread_high_priority needs to be fast and run uninterruptedly. I currently don't know if there is a case where strex always failed in the thread_high_priority context that could cause deadlock.

_Atomic uint32_t a, b, c;
atomic_uint seqcount = 0;

void thread_high_priority(void)
{
  uint32_t _a, _b, _c;
  
  uint orig_cnt = atomic_load_explicit(&seqcount, memory_order_relaxed);

  atomic_store_explicit(&seqcount, orig_cnt + 1, memory_order_relaxed);
  atomic_thread_fence(memory_order_release);

  _a = atomic_load_explicit(&a, memory_order_relaxed);
  _b = atomic_load_explicit(&b, memory_order_relaxed);
  _c = atomic_load_explicit(&c, memory_order_relaxed);
  atomic_store_explicit(&a, _a - 1, memory_order_relaxed);
  atomic_store_explicit(&b, _b + 1, memory_order_relaxed);
  atomic_store_explicit(&c, _c - 1, memory_order_relaxed);

  atomic_store_explicit(&seqcount, orig_cnt + 2, memory_order_release);
}

void thread_low_priority(void)
{
  uint32_t _a, _b, _c;
  
  uint c0, c1;
  do {
    c0 = atomic_load_explicit(&seqcount, memory_order_acquire);

    _a = atomic_load_explicit(&a, memory_order_relaxed);
    _b = atomic_load_explicit(&b, memory_order_relaxed);
    _c = atomic_load_explicit(&c, memory_order_relaxed);

    c1 = atomic_load_explicit(&seqcount, memory_order_acquire);
  } while (c0 & 1 || c0 != c1);
}

Edit: Again after checking the output from compiler, I slightly modify my code in thread_high_priority . Compile using ARM gcc 10.3.1 (2021.10 none) with compilation flag -O1 -mcpu=cortex-m3 -std=gnu18 -mthumb .

In my original code, dmb ish is issued before the store as shown below.

atomic_store_explicit(&seqcount, orig_cnt + 1, memory_order_release);
--->
        adds    r1, r2, #1
        dmb     ish
        str     r1, [r3]

After I separate the memory barrier from store, dmb ish is issued after store, so that the update of seqcount is visible before updating a, b, c .

atomic_store_explicit(&seqcount, orig_cnt + 1, memory_order_relaxed);
atomic_thread_fence(memory_order_release);
-->
        adds    r1, r2, #1
        str     r1, [r3]
        dmb     ish

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM