我可以选择使用一堆互斥体或为一个对象使用一个互斥体。
如果你有很多线程并且对对象的访问经常发生,那么多个锁会增加并行性。以可维护性为代价,因为更多的锁定意味着更多的锁定调试。
锁定互斥体的效率如何? IE。可能有多少汇编指令以及它们需要多少时间(在互斥锁未锁定的情况下)?
精确的汇编指令是最少的开销a mutex - 内存/缓存一致性担保是主要的开销。并且更少地使用特定的锁 - 更好。
互斥体由两个主要部分组成(过于简单化):(1)指示互斥体是否锁定的标志和(2)等待队列。
标志的更改只需几条指令,通常无需系统调用即可完成。如果互斥量被锁定,系统调用将发生将调用线程添加到等待队列并开始等待。如果等待队列为空,则解锁很便宜,但否则需要系统调用来唤醒等待进程之一。 (在某些系统上,廉价/快速的系统调用用于实现互斥体,只有在发生争用的情况下它们才会变成慢速(正常)系统调用。)
锁定未锁定的互斥量确实很便宜。解锁互斥锁而不发生争用也很便宜。
互斥体的成本是多少?拥有大量互斥锁会带来问题吗?或者我可以在代码中抛出与 int 变量一样多的互斥变量,但这并不重要?
您可以根据需要在代码中添加任意数量的互斥变量。您仅受应用程序可以分配的内存量的限制。
概括。用户空间锁(尤其是互斥体)很便宜并且不受任何系统限制。但它们太多会给调试带来噩梦。简单表格:
- 更少的锁意味着更多的争用(缓慢的系统调用、CPU 停顿)和更少的并行性
- 更少的锁意味着调试多线程问题的问题更少。
- 更多的锁意味着更少的争用和更高的并行性
- 更多的锁意味着更多的机会陷入不可调试的死锁。
应该找到并维护应用程序的平衡锁定方案,通常平衡#2 和#3。
(*) 很少被锁定的互斥体的问题是,如果您的应用程序中有太多锁定,则会导致大量 CPU/核心间流量从其他 CPU 的数据缓存中刷新互斥体内存,以保证缓存一致性。缓存刷新就像轻量级中断并由 CPU 透明地处理 - 但它们确实引入了所谓的stalls(搜索“摊位”)。
停顿是导致锁定代码运行缓慢的原因,通常没有任何明显的迹象表明应用程序运行缓慢。 (有些架构提供 CPU/核心间的流量统计信息,有些则不提供。)
为了避免这个问题,人们通常采用大量的锁来减少锁争用的可能性并避免停顿。这就是为什么存在不受系统限制的廉价用户空间锁定的原因。