【发布时间】:2020-05-29 00:08:50
【问题描述】:
我正在尝试为 ARM 编写一个单一的生产者单一消费者队列,我想我已经接近了 DMB,但需要一些检查(我更熟悉 std::atomic。)
这是我所在的位置:
bool push(const_reference value)
{
// Check for room
const size_type currentTail = tail;
const size_type nextTail = increment(currentTail);
if (nextTail == head)
return false;
// Write the value
valueArr[currentTail] = value;
// Prevent the consumer from seeing the incremented tail before the
// value is written.
__DMB();
// Increment tail
tail = nextTail;
return true;
}
bool pop(reference valueLocation)
{
// Check for data
const size_type currentHead = head;
if (currentHead == tail)
return false;
// Write the value.
valueLocation = valueArr[currentHead];
// Prevent the producer from seeing the incremented head before the
// value is written.
__DMB();
// Increment the head
head = increment(head);
return true;
}
我的问题是:我的 DMB 位置和理由是否准确?还是仍然理解我失踪了?在处理由其他线程(或中断)更新的变量时,我特别不确定条件是否需要一些保护。
【问题讨论】:
-
是的,屏障然后存储 =
tail的释放存储,然后加载屏障 = 获取负载。但是您正在使用加载的值 before 您对其设置屏障,以便根据来自另一个线程的值进行比较和分支。在使用currentHead/Tail之前,最好将DMB 放在分支之后,因为您没有mo_consume样式依赖排序的数据依赖。我不确定这是否有问题。 -
通常最好让 std::atomic 为您发出
dmb,而不是使用编译时 + 运行时内存屏障,但对于 ARM,您最终可能会浪费屏障;如果您只需要获取订单,您可能只需要一个总数吗?不,您可能需要在tail上获取和在head上发布。请注意head = increment(head)可以使用increment(currentHead);无需重新加载共享变量,因为该线程是唯一修改它的线程(SPSC)。 -
我是这么认为的。可能的故障模式是
push直到存储到valueArr[currentTail]之后才检查nextTail == head(当乱序推测执行开始加载tail并验证预测路径时)。所以它可能会踩到pop没有读完的值。如果您在 asm 中编写了整个函数,如果您使用了基于比较条件而不是分支的谓词加载或存储,那么您可能可以通过consume样式的排序来避免这种情况。但是在 C 中,你无法控制它是数据依赖还是控制依赖。 -
当然,如果这是单核设备,那么您不需要任何运行时障碍,只需要编译器障碍(
atomic_signal_fence()或asm("" ::: "memory");)。上下文切换和中断将在同一个内核上运行,从而保留所有指令按程序顺序运行的错觉。 -
编译器屏障编译为零 asm 指令。您不需要 ARM 指令(如果这就是您所说的“命令”),只需一个 C 函数/内置函数,如 GNU C
asm("" ::: "memory")。我建议将它隐藏在像barrier()这样的宏后面,这样如果你将来需要,你可以重新定义它以包含运行时屏障,就像 Linux 内核一样:非 SMP 构建可以定义屏障宏来阻止编译 -时间重新排序。
标签: c++ arm synchronization lock-free memory-barriers