此信封实现是否正确使用C ++ 11原子?

Jos*_*vin 7 c++ multithreading memory-model thread-safety stdatomic

我编写了一个简单的“信封”类,以确保我正确理解C ++ 11原子语义。我有一个标头和一个有效负载,编写器清除该标头,填充有效负载,然后用递增的整数填充标头。这样的想法是,读取器然后可以读取标头,将有效负载换出,再次读取标头,如果标头相同,则读取器可以假定他们成功复制了有效负载。读者可能会错过一些更新是可以的,但是让他们获得更新的撕裂(其中来自不同更新的字节混合在一起)也不是可以的。永远只有一个读者和一个作家。

编写者使用释放内存顺序,而读者使用获取内存顺序。

是否存在通过原子存储/加载调用对memcpy重新排序的风险?还是可以将负载彼此重新排序?这永远不会让我流产,但也许我很幸运。

#include <iostream>
#include <atomic>
#include <thread>
#include <cstring>

struct envelope {
    alignas(64) uint64_t writer_sequence_number = 1;
    std::atomic<uint64_t> sequence_number;
    char payload[5000];

    void start_writing()
    {
        sequence_number.store(0, std::memory_order::memory_order_release);
    }

    void publish()
    {
        sequence_number.store(++writer_sequence_number, std::memory_order::memory_order_release);
    }

    bool try_copy(char* copy)
    {
        auto before = sequence_number.load(std::memory_order::memory_order_acquire);
        if(!before) {
            return false;
        }
        ::memcpy(copy, payload, 5000);
        auto after = sequence_number.load(std::memory_order::memory_order_acquire);
        return before == after;
    }
};

envelope g_envelope;

void reader_thread()
{
    char local_copy[5000];
    unsigned messages_received = 0;
    while(true) {
        if(g_envelope.try_copy(local_copy)) {
            for(int i = 0; i < 5000; ++i) {
                // if there is no tearing we should only see the same letter over and over
                if(local_copy[i] != local_copy[0]) {
                    abort();
                }
            }
            if(messages_received++ % 64 == 0) {
                std::cout << "successfully received=" << messages_received << std::endl;
            }
        }
    }
}

void writer_thread()
{
    const char alphabet[] = {"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
    unsigned i = 0;
    while(true) {
        char to_write = alphabet[i % (sizeof(alphabet)-1)];
        g_envelope.start_writing();
        ::memset(g_envelope.payload, to_write, 5000);
        g_envelope.publish();
        ++i;
    }
}

int main(int argc, char** argv)
{
    std::thread writer(&writer_thread);
    std::thread reader(&reader_thread);

    writer.join();
    reader.join();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*ing 5

这称为seqlock;它有一个数据的比赛,因为冲突的调用的简单memsetmemcpy。已经有人提议提供一种类似memcpy的工具来使这种代码正确。在最近不太可能C ++ 26(即使批准)出庭。