如何使用原子操作正确同步多个读取器/单个写入器线程?

时间:2018-01-05 22:37:07

标签: c++ multithreading concurrency c++17 lock-free

我正在开发一种数据处理系统,其中信息的传入部分不断地流入,得到缓冲,然后偶尔进行处理。所以我使用双缓冲方法,其中传入的信息存储在一个缓冲区中,而另一个缓冲区被处理。像这样:

struct data_bundle_1 { /* ... */ };
struct data_bundle_2 { /* ... */ };
// ...
struct data_bundle_n { /* ... */ };

struct data_buffer {
    data_bundle_1 data_1;
    data_bundle_2 data_2;
    // ...
    data_bundle_n data_n;

    std::atomic_int buffer_writer_count;
};

data_buffer double_buf[2];
std::atomic<data_buffer*> current_buf;
int current_buf_index = 0;

数据缓冲区由多个写入程序线程填充,因此data_buffer结构中的每个数据包都将具有内部锁(最可能是基于atomic_flag的自旋锁)以保护其免受可能的数据争用

我不太确定的是如何在切换缓冲区时避免数据争用。在使用current_buf时,数据编写者成为读者,缓冲切换器是作者。

这是我想到的解决方案,但我不确定我的方法是否正确。

// threads #1, 2, ..., m
void data_writer_routine (/* data batch */) {
    data_buffer* cur_buf = current_buf.load(std::memory_order_acquire); // (1)
    cur_buf->buffer_writer_count.fetch_add(1, std::memory_order_release); // (2)

    {
        // in case buffer switching happened between (1) and (2)
        data_buffer* new_cur_buf = curent_buf.load(std::memory_order_acquire);

        if (cur_buf != new_cur_buf) {
            cur_buf->buffer_writer_count.fetch_sub(1, std::memory_order_release);
            (cur_buf = new_cur_buf)->buffer_writer_count.fetch_add(1, std::memory_order_release);
        }
    }

    /* add the data batch to the buffer */

    cur_buf->buffer_writer_count.fetch_sub(1, std::memory_order_release); // (3)
}

// thread #m+1
void buffer_switching_routine () {
    data_buffer old_buf = current_buf.exchange(
        &(double_buf[(current_buf_index=1-current_buf_index)]),
        std::memory_order_release); // (4)

    // waiting till all the data writers are done with the old buffer
    while (old_buf->buffer_writer_count.load(std::memory_order_acquire)); // (5)

    /* process the data from old_buf */
}

基本上,我想了解三件事:

  1. 语句(4)由线程#m + 1执行后(即可以切换缓冲区的唯一线程),是否保证后续执行语句(1)由线程#1-m将返回(4)中设置的值?
  2. 我是否正确使用了内存顺序常量,即在同一个线程内对同一个原子进行多次后续获取操作是否可行,是否可以在每个单独的发布操作中进行多次获取操作(以及每个单独的多个发布操作)跨多个线程获取操作?
  3. 如果这整个结构有问题(即不能保证我在第1条和第(4)和第(5)之后的问题是由#m + 1号线完成的,其中一个线程#1-m可能执行(1)并接收&#34; old&#34;值,从而打开数据竞争的道路),使用原子操作同步这些线程的正确方法是什么?我知道std::shared_mutex,但我真的希望避免因使用内核级同步而导致的性能损失。

0 个答案:

没有答案