我有一个大型数组double*
,并且有多个线程写入它。
我使用boost::mutex
保护每个写入,但这会引入争用并使一切变得非常缓慢,几乎不平行。
有没有更好的方法来控制对我的阵列的多线程写访问?
具体来说,我如何利用它,在我的例子中,数组是稀疏的,每个线程通常写入数组的不同部分;对同一索引的并发写入应该是罕见的,主要发生在一些数组索引上。
编辑:准确地说,每个线程在多个数组索引上使用+=
增加值。
答案 0 :(得分:3)
使用消息队列。使入队方法以原子方式更新(即单指针交换),您应该能够恢复并发性。然后从队列中读取一个单独的(单个)线程并写入该数组。
我可以通过更多信息来详细说明正在执行的更新类型。但总的来说,您可以找到许多可以帮助您执行此操作的无锁队列实现(例如here)。
编辑以回答OP编辑:您将要构建一个存储索引对列表和更新值(或更新函数)的类。
class UpdateMessage {
public:
vector<Pair<int, int>> updates;
}
或类似的东西。然后,读者可以获取更新消息并迭代该矢量,执行给定消息的所有更新。
假设可以在不锁定数组的情况下计算更新,这是一个快速而又脏的实现,应该满足您的要求。
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 。