简体   繁体   English

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

[英]Volatile member variables vs. volatile object?

I'm trying to implement a multiple producer (via interrupt), single consumer (via application thread) queue on an embedded target in "MpscQueue.h" below.我正在尝试在下面的“MpscQueue.h”中的嵌入式目标上实现多生产者(通过中断)、单消费者(通过应用程序线程)队列。

I'm wondering if I can safely remove the some of the volatile usage below (see inline questions).我想知道我是否可以安全地删除下面的一些volatile用法(请参阅内联问题)。 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.我还会考虑使用volatile std::array代替下面显示的 C 样式buffer_[] ,但我不确定我是否可以相信它的实现与下面的意图相匹配。 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.第三种选择是将 MpscQueue 对象本身标记为volatile并限定相关方法volatile ,但尚不清楚这是否会导致所有成员变量(以及它们指向的内容,在指针的情况下)被视为 volatile 。

Any guidance on this?有什么指导吗?

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

Edit: 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 is pretty much useless for thread synchronization -- its primary use is for interacting with memory-mapped devices. Volatile 对于线程同步几乎没有用处——它的主要用途是与内存映射设备进行交互。

Here's what I will do regarding the private member variables, with rationale in comments:以下是我将如何处理私有成员变量,并在评论中说明理由:

T volatile buffer_[depth];  // will never touch buffer_[] via array handle,
                            //   but don't want compiler to optimize it out;
                            //   and technically, the elements are volatile due to push()
T volatile* const begin_;   // buffer_[] has elements of type 'T volatile', so
                            //   keep type of pointer consistent with what it points to
T volatile* const end_;     // "
T volatile* volatile head_; // value must be volatile, as unknown ISR thread will touch;
                            //   also, keep type of pointer consistent
                            // pointer should be volatile since ISRs will read outside
                            //   of "main" thread context
T volatile* volatile tail_; // value should be volatile since multiple ISRs will touch;
                            //   also, keep type of pointer consistent
                            // pointer should be volatile since multiple ISRs will touch

If I used a std::array instead of buffer_[] , I'm not sure how I would enforce that not only the elements of the array, but also the underlying pointers/iterators were also volatile.如果我使用std::array而不是buffer_[] ,我不确定我将如何强制不仅数组的元素而且底层指针/迭代器也是易变的。 Eg, std::array<T volatile, uint32_t depth> volatile ?例如, std::array<T volatile, uint32_t depth> volatile

If I made the whole MpscQueue object volatile, I'm not sure how I would enforce that the 'volatility' would not only trickle down to the pointers themselves (ie * volatile ), but also to the pointed-to values (ie T volatile* volatile instead of just T* volatile ).如果我将整个MpscQueue对象设置为 volatile,我不确定我将如何强制“波动性”不仅会渗透到指针本身(即* volatile ),还会渗透到指向的值(即T volatile* volatile而不仅仅是T* volatile )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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