什么是Mutex
mutex可视作是spinlock的可睡眠版本,同样是线程无法继续向前执行,但spinlock是"spin",导致该CPU上无法发生线程切换,而mutex是"block"(我们通常翻译成「阻塞」),可以发生线程切换,让所在CPU上的其他线程继续执行。阻塞既可以发生在线程试图获取mutex时,也可以发生在线程持有mutex时。
实现
|
|
简单地说就是一个spinlock搭配一个代表等待队列的双向链表"wait_list",外加一个记录mutex生命周期的状态变化的"owner"。没有线程持有mutex时,“owner"的值为0,有线程持有时,需将"owner"转换为指向该线程的"task_struct"的指针,(这里直接引用了引文2的中原话)。
加锁
线程试图加锁的调用是mutex_lock_interruptible()
,该调用替代了mutex_lock()
,实现可以被信号打断的设计。
|
|
- 如果mutex没有被持有,通过fast path 直接持有
- 如果mutex已经被持有,线程被存放到wait_list 中等待mutex的释放,进入slow path
乐观锁的优化
进入wait_list 后,等待锁释放后被再次唤醒,会发生两次上下文切换 乐观锁(optimistic spinning)认为,如果目前在mutex的持有者在执行,临界区的执行一般是比较简单,那么我就不进入睡眠,像spinlock一样等待锁的释放,这样一定比睡眠后再唤醒的速度要快。
|
|
|
|
以这种方式等待的线程被称为spinner,此时任务的状态处理“TASK_RUNNING”,代码的路径被称为mid path 。而相对的,进入wait_list中的线程被称为waiter。
但是这种乐观等待并非没有条件,只有当前没有优先级更高的任务时,才可以以spinner的身份等待锁,并且如果在该线程再执行临界区的时候被抢占,或者当前owner睡眠,都会导致spinner重新编程waiter,加入wait_list 。
解锁
由持有锁的线程来释放lock,如果有等待再wait_list 上的线程,则调用wake_up_q()
唤醒线程
死锁问题
Linux采用 ww-mutex 机制来处理死锁问题,其实就是死锁的一方主动让出
Spinlock VS mutex
Spinlock和mutex的核心差异在于线程在试图获取锁期间,调度是否是关闭的。spinlock被持有时,禁止调度,而mutex是支持调度的。
[1] AWei’s Kernel Tour | 【同步机制】SpinLock (cnjslw.github.io)