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