易失性成员变量与易失性对象?

2022-01-25 00:00:00 embedded c++ volatile interrupt

我正在尝试在下面的MpscQueue.h"中的嵌入式目标上实现一个多生产者(通过中断)、单个消??费者(通过应用程序线程)队列.

I'm trying to implement a multiple producer (via interrupt), single consumer (via application thread) queue on an embedded target in "MpscQueue.h" below.

我想知道是否可以安全地删除下面的一些 volatile 用法(请参阅内联问题).我还会考虑使用 volatile std::array 代替下面显示的 C 风格 buffer_[],但我不确定我是否可以相信它的实现与下面的意图.第三种选择是将 MpscQueue 对象本身标记为 volatile 并限定相关方法 volatile,但尚不清楚这是否会导致所有成员变量(以及它们指向,在指针的情况下)被视为易失性.

I'm wondering if I can safely remove the some of the volatile usage below (see inline questions). I would also consider using volatile std::array in place of the C-style buffer_[] shown below, but I was unsure if I could trust its implementation to match the intent of the below. A third alternative would be to mark the MpscQueue object itself as volatile and qualify the relevant methods volatile, but it's not clear if that would result in all member variables (and what they point to, in the case of pointers) being treated as volatile.

对此有何指导?

template<typename T, uint32_t depth>
class MpscQueue
{
public:
    MpscQueue(void);
    bool push(T& t);
    bool pop(T* const t);

private:
    T volatile buffer_[depth];  // Q1: is volatile unnecessary if never access buffer_[]?
    T volatile* const begin_;   // Q2: is volatile unnecessary if never access value
    T volatile* const end_;     //     via begin_/end_?
    T volatile* head_;          // volatile required so that thread always checks value
    T volatile* volatile tail_; // Q3: is 'T volatile' required so that ISR accounts
                                //     for other ISRs when setting value?
                                // Q4: is '* volatile' required so that ISR accounts
                                //     for other ISRs when checking pointer?
};

template<typename T, uint32_t depth>
MpscQueue<T, depth>::MpscQueue(void) :
    begin_(&buffer_[0]),
    end_(&buffer_[depth - 1]),
    head_(begin_),
    tail_(begin_)
{}

template<typename T, uint32_t depth>
bool MpscQueue<T, depth>::push(T& t)
{
    // "Multiple producer" ISRs can use this function to push at tail

    // Pseudo-code: if not full, *(tail_++) = t
}

template<typename T, uint32_t depth>
bool MpscQueue<T, depth>::pop(T* const t)
{
    // "Single consumer" thread can use this function to pop at head

    // Pseudo-code: if not empty, *t = *(head_++)
}

<小时>

为了将问题集中在正确的方向,让我澄清一下我已经处理了线程安全,这不是这里问题的一部分.


To focus the question in the right direction, let me clarify that I have taken care of thread-safety, and it is not part of the question here.

因为这个队列是单一消费者,所以在读/弹出端不需要线程安全.在写入/推送方面,中断之间的线程安全将通过将所有相关中断设置为相同的优先级来处理(否则将使用锁).

Because this queue is single consumer, there is no thread safety required on the read/pop side. On the write/push side, thread safety among interrupts will be handled by setting all relevant interrupts to the same priority level (otherwise, a lock will be used).

推荐答案

正如所写的那样,代码不是中断安全的――如果在主线程执行读取/弹出操作时发生中断,则存在竞争条件,并且数据结构很可能已损坏.解决此问题的方法是在主线程执行读取/弹出操作时阻止中断.如果您这样做(并且阻止/取消阻止中断的函数是内存屏障),那么易失性都将变得无关紧要并且可以被删除.

As written the code is not interrupt-safe -- if an interrupt occurs while the main thread is doing a read/pop, you have a race condition, and the data structure is likely to be corrupted. The way to fix this is to block interrupts in the main the thread whenever it does a read/pop. If you do that (and the functions that block/unblock interrupts are memory barrieres), the volatiles all become irrelevant and can be removed.

Volatile 对于线程同步几乎没有用处――它的主要用途是与内存映射设备进行交互.

Volatile is pretty much useless for thread synchronization -- its primary use is for interacting with memory-mapped devices.

相关文章