当一个互斥锁已经被T1锁定,并且T2试图锁定它时,T2的过程是什么?
我认为它是这样的:
-T2尝试锁定,失败,可能稍微旋转一下,然后调用yield ...
-T2计划执行几次,尝试锁定失败,产生...
- 最终T1解锁,T2计划执行并设法锁定互斥锁...
T1解锁是否明确地向调度程序或其他线程发出互斥锁被解锁的信号?或者它只是解锁,并让调度程序在感觉适合时调度被阻塞的线程(也称调度程序没有阻塞线程的概念,并且不将它们视为特殊)?
答案 0 :(得分:6)
这取决于您的操作系统。我看到只是旋转,在yield
中旋转,内核中的通用条件变量,用户空间控制的调度以及具有内核支持的专用锁定原语。
使用yield
进行旋转和旋转的表现非常糟糕。从理论上讲,用户控制的调度(见Scheduler activations)应该具有最佳性能,但据我所知,没有人能够在所有情况下都能正常工作。内核中的通用条件变量和具有内核支持的专用锁定原语应该与Linux中的futex或多或少相同,这是后者的最佳示例。
在某些情况下,纺纱可以有更好的性能。在Solaris中,内核中的某些锁定原语具有自适应模式,只要持有锁的进程在不同的cpu上运行,锁就会自动旋转。如果锁具所有者睡觉或被抢占,锁定服务员也会进入睡眠状态。在其他内核中,存在锁定类别,锁定所有者在锁定时不能被抢占或休眠,因此在这些情况下,旋转也很有效。总的来说,特别是在用户空间中,旋转具有如此可怕的退化情况(旋转过程旋转直到它被抢占以让锁拥有者运行)这对于性能非常糟糕。请注意,像futex
这样的专用锁定原语可以实现这样的优化,这是通用条件变量通常不能实现的。
答案 1 :(得分:3)
简而言之:是的,也许......
这是实现细节,如果不知道你在谈论哪个操作系统,就很难说。通常,解锁互斥锁只会将等待线程标记为“可运行”,但不会(必然)在此时调用调度程序 - 即使调用调度程序,也不意味着T2将成为下一个线程跑步。
在Linux中,代码进入mutex_unlock()
,检查是否有任何等待任务(通过检查锁定数是否小于零 - 从1开始解锁,单个锁定请求获取它为零,进一步锁定的尝试将使其成为负数)。如果有进一步的等待过程,它会调用“慢速路径解锁”,通过几个重定向功能来实现详细信息,最终在__mutex_unlock_common_slowpath
- 更进一步向下,有一个调用{最终以try_to_wake_up
结尾的{1}} - 基本上只是将任务排队为“准备运行”,然后调用调度程序(通过几层函数!)
答案 2 :(得分: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.
*系统调用解锁应通知所有被阻止的任务/进程/线程,这些被阻止在互斥锁的锁定调用即可。然后他们计划执行。这并不意味着他们被执行,因为可能已经有人执行。正如其他人所说,这取决于实施方式。如果你真的想学好这点,我建议你book