C++11线程的读写锁

21

我想使用新的标准线程来代替boost:threads,但我注意到旧的shared_mutex不可用。有什么好的建议来替换这个功能并给我一个多读单写锁?


1
我认为更好的重复问题应该是https://dev59.com/FWYq5IYBdhLWcg3wpyRE。当前的重复问题要求手动实现,而另一个则要求标准化的读/写锁。 - Philipp Claßen
2个回答

24

std::shared_mutex将成为C++14标准库的一部分。它没有被纳入C++11标准只是因为没有时间制定提案并进行充分讨论。

您仍然可以使用boost::shared_mutex。在Windows环境下,如果您正在使用Windows Vista或更高版本,则可以使用Slim读写锁,它们针对速度和内存消耗进行了优化。


pthreads在Linux上也有pthread_rwlock - pyCthon
10
std::shared_mutex 在 C++14 中不存在,但将在 C++17 中加入。 - Deqing

16
您应该查看Stack Overflow问题“C++11 equivalent to boost shared_mutex”,特别是下面链接的电子邮件对话:http://permalink.gmane.org/gmane.comp.lib.boost.devel/211180(其中解释了C++11委员会拒绝批准shared_mutex的原因)。此外,您还可以参考Joe Duffy's博客上的以下实验:http://www.bluebytesoftware.com/blog/2009/02/12/ReaderwriterLocksAndTheirLackOfApplicabilityToFinegrainedSynchronization.aspx
每当您考虑使用读写锁时,请自问以下6个问题。如果您对任何一个问题的回答是否定的,则读写锁只会使您的程序变得更糟糕,而不是更好。
  1. 我的共享对象是const吗?我一生中见过更多错误使用shared_mutex的情况,而不是正确使用。要正确使用shared_mutex,必须声明在读取器临界区内能够将共享对象声明为const且没有任何编译器警告。"消费者"并不等同于"完全不修改数据结构的人"。
  2. 我的临界区真的很长吗?锁定shared_mutex比锁定常规mutex要昂贵得多。您必须有大量工作在临界区内,才能弥补锁定获取和释放的开销。
  3. 我的关键段是否太长了? 你应该问自己是否真的需要在关键段中执行所有这些工作。通常会有一些准备工作和/或用来调整返回对象的工作,周围包含对共享对象进行const 调用。从第一次使用共享对象到最后一次使用共享对象的数据依赖路径以外的许多额外工作可以移至关键段之外。
  4. 锁争用真的是我的性能问题吗?即使您的关键段很长,也必须确信锁争用确实是您的性能问题。如果您没有遇到显著的锁争用,则切换到读者/写者锁不会给您带来任何好处。
  5. 通过切换到更细粒度的锁定方案来减少锁争用可能吗?您是否正在使用单个锁来保护多个对象?您能否为每个对象分配一个锁?
  6. 读者与写入者的比率是否明显高于1:1? 即使您的关键段很长且锁争用是一个严重的问题,读者与写入者的比率也需要非常高才能从读者/写者锁中获得任何好处。这取决于您的硬件上原子指令的成本和特定实现的质量。 (Joe Duffy发现在他的机器上,他需要一个约20:1读者:写入者的比率才能使读者/写者锁获胜。)

4
尽管Joe Duffy的那篇文章需要谨慎阅读,但是我们需要注意一点:因为.NET中的某种读写锁(一种特定类型的读写锁的一个实现)表现出某种行为,并不能说明它对于所有的读写锁都适用。读写锁并不一定比临界区对象慢,也不一定要使用CAS操作。由于有很多变体,例如读写锁不一定公平(或者说可能需要公平性)。根据具体情况,读写锁可以比互斥锁高效得多(高达两到三个数量级)。此外,对于读写比例的看法也值得一笑... - Damon
3
在使用1:20比例时,这种做法并不是特别特殊或有趣。使用不合适的工具进行任务会导致最好只能获得平庸的结果。这就像抱怨在随机访问时使用std::list性能差,或者在前面插入数百万个元素时使用std::vector性能差一样。如果你的读操作不超过写操作,那么rw-locks也不会有什么优劣之分(可能还会更糟)。但那是使用了错误的工具。 - Damon
1
@WanderingLogic,您能详细说明第一点吗?那什么是消费者呢?为什么需要编译时const严格性? - aberaud
1
@Wagaf:我可能没有表达清楚。我试图对比读者/写者模式和生产者/消费者模式。生产者/消费者模式的一个例子是工作队列:生产者将项目插入队列,消费者从队列中删除项目。 "删除"是一种变异操作,因此它与"阅读"不同。 - Wandering Logic
2
关于编译时常量的严格性。我并不是在说你必须声明你的对象为const。相反,你只需要确保线程在修改共享对象时始终拥有写锁。你需要完全了解哪些方法会改变你的共享对象。(也就是说,在写锁之外调用共享对象的每个非const方法的实现都必须理解。) - Wandering Logic
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接