【问题标题】:Efficient multi-threaded write access to sparse array?对稀疏数组的高效多线程写访问?
【发布时间】:2014-04-26 18:53:55
【问题描述】:

我有一个大数组double*,多个线程写入它。

我用boost::mutex 保护每个写入,但这会引入争用并使一切变得非常缓慢,几乎不并行。

有没有更好的方法来控制对我的阵列的多线程写访问?

具体来说,我如何利用这一点,在我的例子中,数组是稀疏的,每个线程通常写入数组的不同部分;并发写入同一个索引应该很少见,并且主要发生在少数数组索引上。

编辑:准确地说,每个线程在多个数组索引上使用+= 增加值。

【问题讨论】:

  • 写入大部分是分段的(即,线程写入数据相距很远?)在争用时旋转是否可以接受(即,它真的很少见,优先级反转不是问题吗?)内存开销是否更大比阵列本身可以接受,还是即使在其他地方增加成本也要避免?你有 C++11 支持吗?如果不是,您的线程内存模型是什么?
  • 对数组的访问是连续发生的,还是有保证不被访问的时段?
  • 被操作的索引是否相邻?
  • A double* 不是数组。它是一个指针。
  • 附带说明,如果 double 数组真的很稀疏,那么将其实现为数组确实效率低下。您可能要考虑改用map

标签: c++ arrays multithreading


【解决方案1】:

使用消息队列。使 enqueue 方法自动更新(即单指针交换),您应该能够恢复并发性。然后从队列中读取一个单独的(单个)线程并写入数组。

我可以对此进行扩展,提供更多关于正在执行何种更新的信息。但总的来说,您可以找到许多可以帮助您做到这一点的无锁队列实现(例如 here)。

编辑回答 OP 编辑​​:您需要构建一个类来存储索引对列表和更新值(或更新函数)。

class UpdateMessage {
    public:
    vector<Pair<int, int>> updates;   
}

或者类似的东西。然后,阅读器可以获取更新消息并遍历该向量,执行给定消息的所有更新。


使用 MoodyCamel 队列

假设可以在不锁定数组的情况下计算更新,这是一个快速而肮脏的实现,应该满足您的要求。

using namespace moodycamel;

typedef Updates vector<Pair<int, double>>;

ReaderWriterQueue<Updates> queue(100);
double array[] = initialize_array();
int sleep_interval = 10; // in microseconds, you'll probably want to do something smarter than a
                         // fixed interval here.

void read(ReaderWriterQueue queue) {
    Updates updates;
    bool succeeded = queue.try_dequeue(updates);
    if(succeeded) {
        for(auto it = updates.begin(); it != updates.end(); it = updates.next()) {
            array[it.x] = it.y;
        }
    }
}

void write(ReaderWriterQueue queue, Updates ups) {
    bool succeeded;
    do {
        succeeded = queue.try_enqueue(ups);
        usleep(sleep_interval);
    } while(!succeeded);
}

当然,如果插入失败,这会旋转写入线程。如果这不可接受,您可以直接使用try_enqueue 并在enqueue 失败的情况下做任何您想做的事情。

【讨论】:

  • 如果有另一个线程同时读取数组中的值,它也需要同步,以避免同时读取和写入到同一个内存位置,对吧?跨度>
  • 读取必须更改数组中的值才能为真。就目前而言,从数组中读取的标准方法是将保证是原子操作的元素出列。
  • 不确定我是否理解您的回答。例如,如果我有另一个读取array[34] 值的线程(不修改它),它必须确保写入线程不会同时写入array[34],对吧?
  • 使用这样的无锁队列会阻止您使用这种读/写模式。您不能写入数组 [34],因为写入的唯一方法是将新值排入队列,这会将其放在队列的末尾。读取的唯一方法是将值出列。只要您坚持使用该 API,就会保留无锁保证。
【解决方案2】:

如果您的环境支持 C++11,那么只需将您的双数组替换为任一

  • std::array&lt;std::atomic&lt;double&gt;, N&gt; 用于固定数组,或
  • std::vector&lt;std::atomic&lt;double&gt;&gt; 用于动态数组。

只要不同线程不写入相邻索引(即缓存行的错误共享),性能和可扩展性应该明显优于boost::mutex

【讨论】:

    【解决方案3】:

    如果在访问事件(即数据写入不是连续发生,并且比执行流可以容纳的速度更快)与单个struct 之间存在一定的粒度,那么创建一个线程安全的生产者消费者队列不使用锁的 C++ 将是一个可行的选择。这种方法将允许在高命中频率期间在数据队列中建立数据,然后,随着命中频率的降低,队列的大小将随着数据写入目标而减小 (struct)。最终效果将使您重新获得执行并发性。

    实现的最佳描述(此处不复制)在这里:Creating a thread safe producer consumer queue in C++ without using locks

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多