如何提供对大型数组元素的线程安全访问
Posted
技术标签:
【中文标题】如何提供对大型数组元素的线程安全访问【英文标题】:How to provide thread-safe access to elements of a large array 【发布时间】:2020-06-10 03:29:44 【问题描述】:我在内存中有一个非常大(数十 GB)的 mmap'd 数组,我需要提供线程安全访问。访问模式是频繁读取偶尔写入(添加)。只有几十个线程,所以碰撞的机会很低,但不是零。
我考虑过的事情:
我可以为每个元素创建一个互斥锁,但这在内存方面似乎很浪费。 我可以创建一个 unordered_set 来存储要写入的元素。如果它是一个共享锁,这并不可怕,但每次我写一个双精度时插入和删除在运行时开销方面似乎很重我想知道是否有办法做类似于fetch_add
而不使所有双打原子(特别是因为atomic<double>
在C++ 20 之前大多数情况下都没有在C++ 中实现)。
有没有更好的方法来提供这种线程安全的访问?
【问题讨论】:
也许每 N 个元素有一个std::shared_mutex
?对不同的 N 值进行基准测试可能会发现一个效果很好的值。它不会是“完美的”(排他锁会阻止对未使用元素的访问),但处理可伸缩性时的目标通常不是“完美”而是“足够好”。
操作顺序重要吗?写入线程执行什么样的更新?读取线程是否需要数据的一致性以及任何不变量是否成立?
“atomic<double>
未实现”是什么意思?即使fetch_add
未实现,您也可以始终使用比较和交换操作。
写作线程只是做补充。我不确定如何使用atomic<double>
,因为数组是 mmap 的
【参考方案1】:
您的问题中没有提及数据的一致性,这是最重要的方面。我将尝试描述几种可能的模式,也许其中一种会满足您项目的需求。
首先,让我们想象一下,您的巨大 mmap 中的双精度数据之间可能没有一致性:例如传感器数据,不需要传感器之间的一致性。您可以将atomic<double>
与atomic::store
操作一起使用:您只需存储和读取一些数据,唯一的一致性是每个元素都有新值或旧值(原子保证双精度值是一致的)。如果您需要fetch_add
操作,您可以随时使用compare_exchange_weak
自行实现。
下一个案例:记录应该有一些一致性。您需要定义记录是什么,并在每条记录的基础上拥有一个shared_mutex
(或任何其他锁)。
另一种选择:假设您需要记录之间的一致性,但您不需要它们完全同步。类似于缓存数据的东西,这些数据最终会被更新(C++ 中的宽松内存模型)。您可能对实际数据有一个shared_ptr
。每个读取线程都可以复制共享指针并获得要读取的数据的所有权(这是一种廉价的操作,C++ 保证线程安全)。读取数据后,线程必须销毁共享指针的副本并释放所有权。有趣的部分是写作线程。它将数据副本创建到“交换”内存中,并且该内存由独立的共享指针拥有。写入线程更新数据,它可能需要尽可能多的时间:读取线程不知道存在“交换”并且仍然使用旧数据。一旦写入线程完成它的工作,它就会存储shared_ptr
以将内存交换为实际的数据共享指针。
重要的是:在写入线程提交它的工作时,仍有可能使用旧数据读取线程。这不是问题——他们拥有旧数据的所有权,而这些数据只是稍微过时了。无论如何,新读者都会得到更新的数据。
【讨论】:
在语法上,我不确定如何将atomic<double>
与 mmap 结合使用。 shared_ptr 方法虽然很有趣
如果你有一个指向内存的指针,你可以使用 placement new 操作符来放置任何类型的对象。您可以使用placement new 创建一个原子值数组。以上是关于如何提供对大型数组元素的线程安全访问的主要内容,如果未能解决你的问题,请参考以下文章