Rom*_*lov 6 c++ multithreading c++14
我想实现在C多线程LRU缓存++使用此文章的提示或灵感。它适用于 Go,但所需的概念或多或少也存在于 C++ 中。本文建议在散列表和链表周围使用带有共享互斥锁的细粒度锁定。
因此,我打算使用 编写缓存std::unordered_map,std::list并使用std::shared_timed_mutex. 我的用例包括几个线程 (4-8) 使用此缓存作为拼写错误的单词和相应可能的更正的存储。缓存的大小约为 10000-100000 个项目。
但是我在几个地方读到,使用共享互斥锁而不是普通互斥锁几乎没有意义,而且速度更慢,尽管我找不到一些带有数字的真实基准或至少在何时使用和何时不使用的模糊指南共享互斥锁。而其他来源建议在您有并发读取器或多或少超过并发写入器时使用共享互斥锁。
std::shared_timed_mutex比使用一个普通的更好std::mutex?读者/阅读人数应该多于作者/写作人数多少次?我当然知道这取决于很多因素,但是我应该如何决定使用哪个?std::shared_mutex(从C ++ 17)做比较,定时一个在性能上有什么区别?PS我觉得会有“首先测量/描述最适合你的情况”。我会,但我需要先实现一个,如果有一些启发式可供选择,而不是同时实现选项和测量,那就太好了。此外,即使我进行测量,我认为结果也将取决于我使用的数据。并且很难预测真实数据(例如对于云中的服务器)。
- 什么时候使用 an
std::shared_timed_mutex比使用一个普通的更好std::mutex?读者/阅读人数应该多于作者/写作人数多少次?我当然知道这取决于很多因素,但是我应该如何决定使用哪个?
由于它们额外的复杂性,读/写锁 ( std::shared_mutex, std::shared_timed_mutex) 优于普通锁 ( std::mutex, std::timed_mutex) 的情况很少见。它们确实存在,但就个人而言,我自己从未遇到过。
如果您有频繁但短暂的读取操作,则读/写互斥不会提高性能。它更适合于读取操作频繁且昂贵的场景。当读操作只是在内存数据结构中查找时,很可能简单的锁会胜过读/写解决方案。
如果读取操作非常昂贵并且您可以并行处理许多操作,那么增加读取与写入比率应该在某些时候导致读取/写入器性能优于排他锁的情况。断点在哪里取决于实际工作量。我不知道一个好的经验法则。
另请注意,在持有锁的同时执行昂贵的操作通常是一个坏兆头。可能有更好的方法来解决问题,然后使用读/写锁。
在该领域比我拥有更多知识的人对这个话题发表了两条评论:
- 也许它依赖于平台并且某些平台实现比其他平台更糟糕?(我们使用 Linux 和 Windows 作为目标,MSVC 2017 和 GCC 5)
我不知道操作系统之间的显着差异。我的期望是情况会相似。在 Linux 上,GCC 库依赖于 glibc 的读/写锁实现。如果您想深入了解,可以在pthread_rwlock_common.c 中找到实现。它还说明了读/写锁带来的额外复杂性。
shared_mutexBoost 中的实现存在一个老问题(#11798 - 在 POSIX 上实现 boost::shared_mutex 是次优的)。但是我不清楚这个实现是否可以改进,或者它是否只是一个不太适合读/写锁的例子。
- 实现文章中描述的缓存锁定是否有意义?
坦率地说,我怀疑读/写锁会提高这种数据结构的性能。读取器操作应该非常快,因为它只是一个查找。更新 LRU 列表也发生在读取操作之外(在 Go 实现中)。
一个实现细节。在这里使用链表并不是一个坏主意,因为它使更新操作非常快(您只需更新指针)。使用时std::list请记住,它通常涉及内存分配,当您持有密钥时应避免这种情况。最好在获取锁之前分配内存,因为内存分配很昂贵。
在他们的 HHVM 项目中,Facebook 有并发 LRU 缓存的 C++ 实现,看起来很有希望:
该ConcurrentLRUCache还使用链表(但不是std::list)为LRU列表,并且tbb::concurrent_hash_map对地图本身(从英特尔并行哈希表实现)。请注意,对于 LRU 列表更新的锁定,它们不像 Go 实现中那样采用读/写方法,而是使用简单的std::mutex排他锁。
第二个实现 ( ConcurrentScalableCache) 建立在ConcurrentLRUCache. 他们使用分片来提高可扩展性。缺点是 LRU 属性只是近似值(取决于您使用的分片数量)。在某些可能会降低缓存命中率的工作负载中,这是一个很好的技巧,可以避免所有操作都必须共享相同的锁。
- std::shared_mutex(来自 C++17)与定时互斥对性能有什么影响吗?
我没有关于开销的基准数字,但它看起来像是在比较苹果和橙子。如果您需要计时功能,您别无选择,只能使用std::shared_timed_mutex. 但是如果你不需要它,你可以简单地使用std::shared_mutex,它必须做更少的工作,因此永远不会变慢。
对于需要超时的典型场景,我不希望计时开销太严重,因为无论如何在这种情况下锁往往会保持更长时间。但如前所述,我无法用实际测量来支持该声明。