本文介绍了在这里需要挥发吗的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在实现一个序列锁定"类,以允许对数据结构进行锁定写入和无锁定读取.

I'm implementing a 'sequence lock' class to allow locked write and lock-free reads of a data structure.

将包含数据的结构包含序列值,该序列值将在写入发生时增加两次.在写入开始之前一次,在写入完成之后一次.编写者在阅读者之外的其他线程上.

The struct that will contain the data contains the sequence value, which will be incremented twice while the write takes place. Once before the writing starts, and once after the writing is completed. The writer is on other threads than the reader(s).

这是保存数据副本的结构,其序列值如下所示:

This is what the struct that holds a copy of the data, and the sequence value looks like:

template<typename T>
struct seq_data_t
{
    seq_data_t() : seq(0) {};
    int seq;                     <- should this be 'volatile int seq;'?
    T data;
};

整个序列锁类在循环缓冲区中保留此结构的N个副本.编写器线程始终在循环缓冲区中覆盖数据的最旧副本,然后将其标记为当前副本.写作是互斥锁.

The whole sequence lock class holds N copies of this structure in a circular buffer. Writer threads always write over the oldest copy of the data in the circular buffer, then mark it as the current copy. The writing is mutex locked.

读取功能未锁定.它尝试读取数据的当前"副本.它在读取前存储"seq"值.然后读取数据.然后,它再次读取seq值,并将其与第一次读取的值进行比较.如果seq值未更改,则认为读数良好.

The read function does not lock. It attempts to read the 'current' copy of the data. It stores the 'seq' value before reading. Then it reads data. Then it reads the seq value again, and compares it to the value it read the first time. If the seq value has not changed, the read is deemed to be good.

由于写程序线程可以在读取时更改'seq'的值,因此我认为seq变量应标记为volatile,以便read函数在读取数据后会显式读取该值.

Since the writer thread could change the value of 'seq' while a read is occurring, I'm thinking that the seq variable should be marked volatile, so that the read function will explicitly read the value after it reads the data.

读取功能看起来像这样:它将在写入器之外的其他线程上,也许在多个线程上.

The read function looks like this: It will be on threads other than the writer, and perhaps several threads.

    void read(std::function<void(T*)>read_function)
    {
        for (;;)
        {
            seq_data_type<T>* d = _data.current; // get current copy
            int seq1 = d->seq;      // store starting seq no
            if (seq1 % 2)           // if odd, being modified...
                continue;           //     loop back

            read_function(&d->data);  // call the passed in read function
                                      // passing it our data.


//??????? could this read be optimized out if seq is not volatile?
            int seq2 = d->seq;      // <-- does this require that seq be volatile?
//???????

            if (seq1 == seq2)       // if still the same, good.
                return;             // if not the same, we will stay in this
                                    // loop until this condition is met.
        }
    }

问题:

1)在这种情况下seq必须是易失的吗?

1) must seq be volatile in this context?

2)在具有多个成员的结构的上下文中,只有volatile限定变量是volatile,而其他成员不是吗?也就是说,如果我仅将"seq"标记为在结构内是易失的,那么它只是易失的吗?

2) in the context of a struct with multiple members, are only the volatile qualified variable volatile, and not the other members? i.e. is only 'seq' volatile if I only mark it volatile within the struct?

推荐答案

当然,seq的读取很有可能会被-O3优化.所以是的,您应该暗示编译器使用volatile关键字在其他位置(即在其他线程中)更改seq.

Sure, most probably the read from seq will be optimized out with -O3. So yes, you should hint the compiler that seq might be changed elsewhere (i.e. in other thread) with volatile keyword.

对于x86架构,这已经足够了,因为x86内存模型(几乎)是连续的,如.

For x86 architecture it would be enough, because x86 memory model is (almost) sequential as described on Wikipedia.

出于可移植性,最好使用原子基元.

For portability, you better use atomic primitives.

否,data也应标记为易失性(或也应使用原子基元).基本上,循环:

No, the data should be marked as volatile as well (or you should use atomic primitives as well). Basically, the loop:

for (;;) {
    seq1 = d->seq;
    read_data(d->data);
    seq2 = d->seq;
    if (seq1 == seq2)
        return;
}

等效于:

read_data(d->data);
return;

因为代码中唯一可见的效果是read_data()调用.

Because the only observable effect in the code is the read_data() call.

请注意,最可能使用-O3编译器的代码将对您的代码进行大量重新排序.因此,即使对于x86体系结构,您也需要在第一次seq读取,data读取和第二次seq读取(即:

Please note, that most likely with -O3 compiler will reorder your code quite extensively. So even for x86 architecture you will need a compiler barriers between first seq read, data read and second seq read, i.e.:

for (;;)
    {
        seq_data_type<T>* d = _data.current;
        int seq1 = d->seq;
        COMPILER_BARRIER();
        if (seq1 % 2)
            continue;

        read_function(&d->data);
        COMPILER_BARRIER();
        int seq2 = d->seq;
        if (seq1 == seq2)
            return;
    }
}

最轻巧的编译器障碍是:

The most lightweight compiler barrier is:

 #define COMPILER_BARRIER asm volatile("" ::: "memory")

对于C ++ 11,您可以改用 atomic_signal_fence().

For C++11 you can use atomic_signal_fence() instead.

总的来说,使用std::atomic<>更安全:它具有更高的可移植性,并且没有像volatiles和编译器屏障那样费劲的麻烦.

Overall, it is safer to use std::atomic<>: it is more portable and not that tricky as juggling with volatiles and compiler barriers...

也请看一下Herb Sutter的演讲"atomic<>武器",它解释了编译器和其他内存障碍以及原子: https://channel9.msdn.com/Shows/Going+Deep/Cpp- and-beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

Please also have a look at Herb Sutter's presentation called "atomic<> Weapons", which explains compiler and other memory barriers as well as atomics: https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

这篇关于在这里需要挥发吗的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 13:06