互斥锁粒度

5
我有一个关于线程的问题。通常情况下,当我们调用mutex(lock)时,这意味着该线程将继续执行代码的一部分,而不会被其他线程中断,直到它遇到mutex(unlock)(至少书上是这么说的)。所以我的问题是,是否实际上可能有几个作用域不相互干扰的WriteLocks。比如说:
如果我有一个包含N个元素的缓冲区,并且没有新元素添加进来,但是频繁更新(比如改变第K个元素的值),是否可以在每个元素上设置不同的锁,以便只有当实际上有两个或更多的线程尝试更新相同的元素时,线程才会停止并等待?

1
为什么那样不起作用? - filmor
5
您的实际问题与标题中提到的“作用域锁”无关。作用域锁使用互斥锁进行锁定,并在它们被销毁时(通常是超出范围时)解锁互斥锁。 - stefaanv
@stefaanv是正确的。请将标题更改为更具描述性的内容。 - Adri C.S.
2
我试图提供一个更匹配的标题。如果你有更好的想法,请随意提出(请不要在标题中加入类似于“C ++”之类的标签,那是标签的作用)。 - Angew is no longer proud of SO
对于混淆造成的不便,很抱歉,英语不是我的母语。谢谢! - Constantine Samoilenko
5个回答

7
回答关于N个互斥锁的问题:是的,这确实是可能的。由互斥锁保护的资源完全取决于您作为该互斥锁的用户。
这导致了您问题的第一个(声明)部分。仅靠互斥锁本身并不能保证线程不被中断。它所保证的只是互斥排斥——如果线程B尝试锁定线程A已经锁定的互斥锁,则线程B将阻塞(不执行任何代码),直到线程A释放该互斥锁。
这意味着互斥锁可以用来保证线程执行一段代码时不被中断;但这仅在所有线程都遵循相同的互斥锁定协议的情况下才有效。这意味着您有责任为每个单独的互斥锁分配语义(或含义),并在代码中正确地遵守这些语义。
如果您决定将语义定义为“我有一个包含N个数据元素的数组a和一个包含N个互斥锁的数组m,只有在锁定m [i]时才能访问a [i]”,那么它就会按照这种方式工作。
需要始终坚持相同协议的需求是为什么通常应该以某种方式封装互斥锁及其保护的代码/数据,以便外部代码不需要了解协议的详细信息。它只知道“调用此成员函数,同步将自动发生”。这种“自动化”将是该类正确实现协议。

谢谢。我确实在我的“Element”类中封装了锁,但我还有一个小的后续问题——假设我有一个“缓冲区”,但现在插入操作的机会很小,据我所知,我需要锁定整个“缓冲区”进行插入操作,因为它会影响用于整个“缓冲区”的数据结构。但是,如果我实际上有一个正在进行的“更新”操作,那么缓冲区锁是否会注意到它,还是会出现运行时错误? - Constantine Samoilenko
2
@ConstantineSamoilenko 听起来你需要为缓冲区使用读写锁,除了每个元素的互斥锁。锁定互斥锁需要先获取读锁。插入需要获取写锁。这样,只有在没有锁定互斥锁时才能进行插入操作。 - Angew is no longer proud of SO
太棒了!谢谢你。 - Constantine Samoilenko

4
在选择每个数组一个互斥锁和每个元素一个互斥锁时,一个关键的考虑因素是是否有一些操作——如跟踪“正在使用”的数组元素数量、“活动”的元素或将指向数组的指针移动到更大的缓冲区——只能由一个线程安全地完成,而其他所有线程都被阻塞。
另一个次要但有时很重要的考虑是更多互斥锁使用的额外内存量。
如果您确实需要在高度竞争的多线程程序中尽快执行此类更新,您可能还想了解无锁原子类型及其比较和交换操作,但我建议不要考虑这一点,除非在整体程序性能中对现有锁定进行了显著的分析。

1

互斥锁并不能完全阻止其他线程运行,它只是阻止其他线程锁定同一个互斥锁。也就是说,当一个线程保持互斥锁被锁定时,操作系统会继续进行上下文切换,让其他线程也运行,但如果任何其他线程试图锁定相同的互斥锁,它的执行将被暂停,直到互斥锁被解锁。

因此,确实可以有几个不同的互斥锁,并且可以独立地对它们进行锁定/解锁。但要小心死锁,即如果一个线程可以同时锁定多个互斥锁,那么可能会遇到这样一种情况:线程1已经锁定了互斥锁A,正在尝试锁定互斥锁B,但由于线程2已经锁定了互斥锁B并且正在尝试锁定互斥锁A,因此线程1会被阻塞。


0

您的使用情况并不完全清晰:

  1. 线程被分配一个缓冲区来处理;
  2. 线程有一些结果,并请求一个特殊的缓冲区进行更新。

对于第一种情况,您需要一些分配逻辑来为线程分配缓冲区。这个逻辑必须以原子方式执行。因此,最好使用互斥锁来保护分配逻辑。

对于另一种情况,最好有一个互斥锁向量,每个缓冲区元素一个互斥锁。

在这两种情况下,缓冲区都不需要保护,因为它(或者更准确地说,它的每个字段)只会被一个线程访问。

您还可以了解有关“信号量”的信息。它们包含一个计数器,允许管理具有有限数量但超过一个的资源。互斥锁是n=1的信号量的一种特殊情况。


互斥锁和二进制信号量的用法相似,但它们并不相同。将互斥锁解释为信号量的特殊情况只有在信号量更老的时候才有意义,因此为了理解互斥锁而学习信号量是不必要的。此外,许多以前需要使用信号量处理的情况现在可以使用条件变量来更清晰地完成(条件变量需要互斥锁,而不是二进制信号量)。 - stefaanv

0

对于每个条目,您可以使用互斥锁。C++11的互斥锁可以很容易地转换为自适应自旋锁,因此您可以在CPU和延迟之间取得良好的平衡。

或者,如果您需要非常低的延迟且拥有足够的CPU,您可以为每个条目使用一个原子的“繁忙”标志,并在争用时在紧密的比较-交换循环中自旋。

根据经验,当并发写操作通过命令队列(或者一队较小的不可变缓冲区,在目标处进行连接)进行序列化并由单个线程处理队列时,可以实现最佳性能和可伸缩性。


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