我有一个巨大的tbb::concurrent_unordered_map
,它被多个(~60个)线程并发地大量“读取”。
每天一次,我需要清除它(无论是完全,或选择性)。擦除显然不是线程安全的tbb
实现,所以一些同步需要到位,以防止UB。
然而,我知道一个事实,“写”每天只会发生一次(确切时间未知)。
我一直在考虑std::shared_mutex
以允许并发读取,但我担心即使在无竞争的情况下也可能会显著减慢速度。
有没有更好的解决方案?
也许在锁定互斥锁之前检查std::atomic<bool>
?
先谢谢你。
2条答案
按热度按时间mepcadol1#
它可能需要一些额外的维护工作,但是您可以使用写入时复制方案。
将Map保存在共享指针内的单一示例中。
对于“读取”操作,让用户线程安全地复制共享指针,并根据需要使用它。
对于“写”操作,在新的共享指针中创建一个新的示例Map,用你想要的任何东西填充它,并替换单例中的旧版本。
这样,“读取”用户仍然可以看到旧的版本,并且可以安全地使用它。只要确保他们偶尔从单例中获得最新的版本。也许,甚至给予他们一个句柄,每秒钟自动更新一次共享指针。
如果您不需要线程一次同步更新所有线程,则可以使用此方法。
另一个方案是,创建原子布尔值来指示何时有更新到来,并且当它为真时,让所有线程暂停对map的操作。一旦它们都停止了,您执行更新并让它们继续操作。
dxxyhpgq2#
对于一个read/write lock来说,这是一个完美的工作。
在C++中,这可以通过
shared_mutex
来实现,然后使用unique_lock
来锁定它以进行写入,并使用shared_lock
来锁定它以进行阅读。示例代码请参见this post。最终效果是读取器永远不会彼此阻塞,读取可以同时发生,但如果写入器拥有锁,则所有内容都将阻塞,以便写入操作继续进行。
如果写入需要很长时间,以至于每天一次的延迟是不可接受的,那么您可以让写入器创建并填充数据的新副本,而不获取锁,然后获取锁的写入端并交换数据:
读者:
1.使用
shared_lock
锁定互斥锁1.做事
1.解除锁定
1.重复播放
作者:
1.创建新的数据副本
1.使用
unique_lock
锁定互斥锁1.快速交换数据
1.解除锁定
1.明天重复
在
shared_mutex
上运行shared_lock
会更快。您可以使用double check locking策略,但我不会这样做,除非您进行性能测试,并且可能还需要查看shared_lock
的源代码,因为我怀疑它已经做了类似的事情。并且在读端进行双重检查可能会增加不必要的开销,而且我还没有足够的咖啡来解决读/写锁场景中的双重检查锁定。还有一个线程结构称为spin lock,但它实际上只是一个双重检查锁的封装,重复“检查”直到它被清除。这是一个很好的结构,但同样,您需要性能分析和查看
shared_lock
+shared_mutex
源代码,因为它们可能已经旋转了。旋转锁can be found here的一个很好的实现,它涵盖了一些常见的陷阱。你可能需要调整它来获得一个读/写自旋锁。一般来说,最好在最初的实现中使用现有的结构,至少作为一个清晰编码的概念验证。然后,如果您 * 知道 * 您看到了太多的读取争用,您可以从那里进行优化。但是您需要首先使用通用策略,并且在100次中有91次是足够好的。在这种情况下,无论如何,读/写锁定的 * 一些 * 表现形式就是您最终将得到的结果。