Mutex锁:什么是“阻塞”意味着?

25

我一直在研究多线程和共享资源访问,其中一个(对我来说)新的概念是互斥锁。但我似乎找不到答案,当一个“关键区域”被锁定时,发现它的线程会发生什么。很多地方都说该线程将被“阻塞”,但这意味着什么呢?它会被暂停,当锁解除时恢复吗?还是会在下一个“运行循环”迭代中尝试再次获取锁?

我提出这个问题的原因是,我想在我的辅助线程的运行循环的一个非常特定的部分中处理系统提供的事件(鼠标、键盘等)。所以无论传递什么事件,我都会将其排队在自己的数据结构中。显然,数据结构需要一个互斥锁,因为它被两个线程修改。缺失的拼图是:如果在主线程上的函数中传递了事件,我想将其排队,但队列被锁定了怎么办?主线程会被暂停吗,还是它只会跳过锁定部分并超出范围(丢失事件)?

4个回答

22

Blocked表示执行被卡在那里;通常情况下,系统使线程进入睡眠状态,并将处理器让给另一个线程。当线程尝试获取互斥锁时被阻塞,当互斥锁被释放时,线程会恢复执行,但如果另一个线程在此之前获得了互斥锁,则该线程可能会再次被阻塞。

通常会有一个try-lock操作用于尝试获取互斥锁,如果不能获取,则会返回错误。但是最终您需要将当前事件移动到该队列中。此外,如果延迟将事件移动到处理它们的线程,则应用程序将变得不响应。

队列实际上是一种可以不使用互斥锁的情况之一。例如,Mac OS X(以及可能也包括iOS)提供了OSAtomicEnqueue()OSAtomicDequeue()函数(请参阅man atomic<libkern/OSAtomic.h>),利用特定于处理器的原子操作来避免使用锁。

但是,为什么不将事件作为主运行循环的一部分在主线程上处理呢?


主运行循环并不是驱动应用程序的主要因素。我在Mac OSX上使用CVDisplayLink,它有效地生成一个单独的(高优先级)线程,该线程将驱动我的运行循环。据我所知,事件将在主线程上传递,因此需要同步。 - zmippie

11
最简单的想法是,被阻塞的线程会进入等待(“睡眠”)状态,直到持有互斥锁的线程释放它。此时,操作系统将“唤醒”等待互斥锁的一个线程,并允许它获取并继续执行。就像操作系统把受阻线程放在架子上,直到它拥有继续执行所需的东西。在操作系统把线程从架子上取下之前,它什么也不做。具体实现——哪个线程能够接下来运行、是否全部唤醒或者排队等——取决于你使用的操作系统和编程语言/框架。

2
那么被阻塞和等待有什么区别,它们是同一件事吗? - Celeritas

5
太晚回答了,但我可以帮助理解。我更多地从实现的角度而非理论文本来谈论。
“阻塞”这个词有点技术同音词。人们可能用它表示“睡眠”或简单的“等待”。术语必须根据使用上下文来理解。
“阻塞意味着等待”——假设在SMP系统上,线程B想要获取由其他线程A持有的自旋锁。其中一种机制是禁用抢占并保持在处理器上自旋,除非B获取它。另一个机制可能是允许其他线程使用处理器,在易于尝试时不获取B。因此,我们调度出线程B(因为启用了抢占),并将处理器分配给某个其他线程C。在这种情况下,线程B只是在调度程序队列中等待,并在轮到它时回来。请注意,B并没有休眠,只是被动地等待,而不是繁忙地等待和消耗处理器周期。在BSD和Solaris系统中,有像“转门”这样的数据结构来实现这种情况。
“阻塞意味着休眠”——如果线程B改为进行像read()这样的系统调用,等待网络套接字的数据,则在获取数据之前它无法继续执行。因此,一些文本随意使用“阻塞”这个术语,如“...在I/O中被阻塞”,或者“...在阻塞系统调用中”。实际上,线程B相当于正在休眠。有特定的数据结构被称为“睡眠队列”,很像机场的豪华候机室:-)。当操作系统检测到数据可用时,线程将被唤醒,就像等候室的服务员一样。

1
这解释得很好!谢谢!我喜欢豪华候车室的比喻。非常生动。 - Bruce

1

阻塞意味着就是这样。它被阻塞了。除非能够继续执行,否则不会进行。您没有说明使用的是哪种语言,但大多数语言/库都有锁对象,您可以“尝试”获取锁,然后根据是否成功执行不同的操作。

但是,在例如Java同步块中,您的线程将停滞,直到能够获取监视器(互斥锁,锁)。java.util.concurrent.locks.Lock接口描述了具有更灵活的锁获取方式的锁对象。


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