对稀疏数组进行高效的多线程写访问?

时间:2014-04-26 18:53:55

标签: c++ arrays multithreading

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

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

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

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

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

3 个答案:

答案 0 :(得分:3)

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

我可以通过更多信息来详细说明正在执行的更新类型。但总的来说,您可以找到许多可以帮助您执行此操作的无锁队列实现(例如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失败的情况下执行您想做的任何事情。

答案 1 :(得分:3)

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

即可
  • std::array<std::atomic<double>, N>用于固定数组,或
  • std::vector<std::atomic<double>>用于动态数组。

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

答案 2 :(得分:2)

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

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