主页
文章
分类
系列
标签
简历
【同步机制】mutex
发布于: 2022-4-5   更新于: 2022-4-5   收录于: Linux Kernel
文章字数: 202   阅读时间: 1 分钟   阅读量:

什么是Mutex

mutex可视作是spinlock的可睡眠版本,同样是线程无法继续向前执行,但spinlock是"spin",导致该CPU上无法发生线程切换,而mutex是"block"(我们通常翻译成「阻塞」),可以发生线程切换,让所在CPU上的其他线程继续执行。阻塞既可以发生在线程试图获取mutex时,也可以发生在线程持有mutex时。

实现

1
2
3
4
5
struct mutex{
	atomic_long_t owner;
	spinlock_t wait_lock;
	struct list_head wait_list ;
};

简单地说就是一个spinlock搭配一个代表等待队列的双向链表"wait_list",外加一个记录mutex生命周期的状态变化的"owner"。没有线程持有mutex时,“owner"的值为0,有线程持有时,需将"owner"转换为指向该线程的"task_struct"的指针,(这里直接引用了引文2的中原话)。

加锁

线程试图加锁的调用是mutex_lock_interruptible(),该调用替代了mutex_lock() ,实现可以被信号打断的设计。

1
2
3
4
5
6
7
8
9
int __sched mutex_lock_interruptible(struct mutex *lock)
{     
	might_sleep();     
	/* fast path */     
	if (__mutex_trylock_fast(lock))          
		return 0;     
	/* slow path */     
	return __mutex_lock_interruptible_slowpath(lock);
}
  • 如果mutex没有被持有,通过fast path 直接持有
  • 如果mutex已经被持有,线程被存放到wait_list 中等待mutex的释放,进入slow path

乐观锁的优化

进入wait_list 后,等待锁释放后被再次唤醒,会发生两次上下文切换 乐观锁(optimistic spinning)认为,如果目前在mutex的持有者在执行,临界区的执行一般是比较简单,那么我就不进入睡眠,像spinlock一样等待锁的释放,这样一定比睡眠后再唤醒的速度要快。

1
2
3
4
5
6
7
struct optimistic_spin_node {
    struct optimistic_spin_node *next, *prev;//spinner本身也成为一个list,加大了维护的成本
    int locked; /* 1 if lock acquired */
    int cpu; /* encoded CPU # + 1 value */};struct optimistic_spin_queue {
    /* Stores an encoded value of the CPU # of the tail node in the queue */
    atomic_t tail;
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
mutex_optimistic_spin(struct mutex *lock, struct mutex_waiter *waiter, ...)
{
    if (!mutex_can_spin_on_owner(lock))
        goto fail;

    osq_lock(&lock->osq)
    ...
}

int mutex_can_spin_on_owner(struct mutex *lock)
{
    if (need_resched())//确定没有更高优先级的任务才能称为spinner
        return 0;
    ...
}

以这种方式等待的线程被称为spinner,此时任务的状态处理“TASK_RUNNING”,代码的路径被称为mid path 。而相对的,进入wait_list中的线程被称为waiter。

但是这种乐观等待并非没有条件,只有当前没有优先级更高的任务时,才可以以spinner的身份等待锁,并且如果在该线程再执行临界区的时候被抢占,或者当前owner睡眠,都会导致spinner重新编程waiter,加入wait_list 。

Pasted image 20231002204502|800

解锁

由持有锁的线程来释放lock,如果有等待再wait_list 上的线程,则调用wake_up_q()唤醒线程

VwvRdRLmmOfdVmze-32049591-45b8-eca1-8f35-ca193fb517cd|905|800

死锁问题

Linux采用 ww-mutex 机制来处理死锁问题,其实就是死锁的一方主动让出

Spinlock VS mutex

Spinlock和mutex的核心差异在于线程在试图获取锁期间,调度是否是关闭的。spinlock被持有时,禁止调度,而mutex是支持调度的。


[1] AWei’s Kernel Tour | 【同步机制】SpinLock (cnjslw.github.io)

[2] Linux 中的 mutex 机制 [一] - 加锁和 osq lock

[3] Linux 中的 mutex 机制 [二] - 解锁和 ww-mutex