如何在C中同时原子地读取多个变量



我试图同时以原子方式读取三个变量a, b, c。该模式类似于下面的代码。

_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;
}

CCD_ 2是以高优先级运行的线程,而CCD_。thread_high_priority可以中断thread_low_priority的执行,但不能中断。也就是说,thread_high_priority将始终不间断地运行。

约束条件是thread_high_priority是时间关键的。因此,我不想使用互斥来进行阻塞,因为这很耗时,甚至会导致死锁。有没有办法确保同时读取所有三个变量而不中断?

编辑:该平台采用ARMv7M架构,运行在裸金属环境中。

您可以使用间接级别来解决此问题。

只要只有一个作家,你就可以这样做:

  • 将数据项集放入结构中
  • 分配几个这样的结构
  • 以非原子方式写入读取器未使用的结构的成员
  • 原子化地更改读取器应该使用的结构的指针

读取器应该读取指针,然后访问相应结构中的数据。

如果在主上下文仍在读取时可能发生另一个中断,那么您需要保留一个指针,指向读取器正在使用的结构,编写器可以在填充结构之前对此进行检查。如果只有一个读取器,那么以原子方式访问第二个指针会更容易。

为了使事情顺利进行,您可以分配三个或更多的structs,并将它们视为一个环形缓冲区。

我还提出了另一个基于SeqLock的解决方案。在知道我试图实现的基本上是撕裂检测之后,我使用SeqLock模板重写了它。我仍然将我的三个变量a, b, c定义为_Atomic uint32_t,因为我也想使用atomic_fetch_*thread_low_priority中修改它们。

在ARMv7-M体系结构上,利用ldrex/strex实现了RMW原子操作。编译器将发出一个循环来检查strex是否成功。在我的情况下,使用RMW操作时可能会出现问题,因为thread_high_priority需要快速且不间断地运行。我目前不知道strexthread_high_priority上下文中是否总是失败,这可能会导致死锁。

_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);
}

编辑:再次检查编译器的输出后,我在thread_high_priority中稍微修改了我的代码。使用带有编译标志-O1 -mcpu=cortex-m3 -std=gnu18 -mthumb的ARM gcc 10.3.1(2021.10 none)进行编译。

在我的原始代码中,dmb ish是在商店之前发布的,如下所示。

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

在我将存储器屏障与存储区分离之后,在存储区之后发出thread_high_priority0,因此在更新a, b, c之前可以看到seqcount的更新。

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