11

当一个 mutex 已被 T1 锁定,而 T2 试图锁定它时,T2 的过程是什么?

我认为它是这样的:

-T2 尝试锁定,失败,可能自旋锁有点,然后调用 yield...
-T2 计划执行几次,尝试锁定失败,yield... -
最终 T1 解锁,T2 计划执行和设法锁定互斥锁...

T1 解锁是否明确向调度程序或其他线程发出互斥锁已解锁的信号?或者它只是解锁,并让调度程序在感觉合适时调度阻塞线程(又名调度程序没有阻塞线程的概念,也不将它们视为特殊)?

4

3 回答 3

6

这取决于您的操作系统。我已经看到yield了内核中的通用条件变量、用户态控制调度和具有内核支持的专用锁定原语。

旋转和旋转yield具有可怕的性能。从理论上讲,用户态控制的调度(请参阅调度程序激活)应该具有最佳性能,但据我所知,没有人让它在所有情况下都能正常工作。内核中的通用条件变量和具有内核支持的专用锁定原语应该与 Linux 中的futex执行或多或少相同,作为后者的最佳示例。

在某些情况下,旋转可以具有更好的性能。在 Solaris 中,内核中的某些锁定原语具有自适应模式,只要持有锁的进程在不同的 cpu 上运行,锁就会旋转。如果锁所有者休眠或被抢占,锁服务员也会进入休眠状态。在其他内核中,有一些锁的所有者在持有锁时不能被抢占或休眠,因此在这些情况下旋转也能很好地工作。但总的来说,特别是在用户态,旋转有如此可怕的退化情况(旋转过程旋转直到它被抢占让锁所有者运行),这对性能非常不利。请注意,专门的锁定原语futex可以实现这样的优化,而通用条件变量通常不能。

于 2013-02-26T13:44:23.787 回答
3

简而言之:是的,也许……

这是实现细节,如果不知道您在谈论哪个操作系统,就很难说。通常,解锁互斥体只会将等待线程标记为“可运行”,但不会(必然)在那时调用调度程序——即使调用了调度程序,也不意味着 T2 将成为下一个线程跑步。

在 Linux 中,代码进入mutex_unlock()which 检查是否有任何等待任务(通过检查锁计数是否小于零 - 它从 1 开始表示未锁定,单个锁请求使其为零,进一步尝试锁定将使其变为负数)。如果有进一步的等待过程,它会调用“慢速路径解锁”,它通过几个重定向函数来允许实现细节,最终会在__mutex_unlock_common_slowpath- 再往下几行,有一个调用wake_up_process最终会在try_to_wake_up-它本质上只是将任务排队为“准备运行”,然后调用调度程序(通过几层函数!)

于 2013-02-26T13:52:12.097 回答
1

假设我们有以下场景:

 1. T1 got M1. M1 locked.
 2. T2 tries to get M1 and gets blocked as M1 is locked.
 3. T3 tries to get M1 and gets blocked as M1 is locked.
 4. ...some time later...
 5. T1 unlocks M1.*
 6. T2 got M1.
 7. T3 is unblocked and tries to get M1 but is blocked again as T2 got M1 first.

*系统调用unlock应该通知所有被互斥锁调用阻塞的阻塞任务/进程/线程。然后他们被安排执行。这并不意味着他们被执行,因为可能已经有人在执行。正如其他人所说,这取决于实现方式。如果你真的想学好,我会推荐这本书

于 2013-02-26T14:02:57.433 回答