general_lock
自旋锁
普通自旋锁 spinlock
相关结构体:
获取锁的过程(即上锁的过程)是自旋(忙等)的,不会引起睡眠和调度
持有自旋锁的临界区中不允许调度和睡眠,自旋锁的加锁操作会禁止抢占,解锁操作时再恢复抢占
自旋锁既可以用于进程上下文,又可以用于中断上下文
自旋锁的主要用途是多处理器之间的并发控制,适用于锁竞争不太激烈的场景
如果锁竞争非常激烈,那么大量的时间会浪费在加锁自旋中,导致整体性能下降
相关API:
读写自旋锁 rwlock_t
普通自旋锁的缺点:
对所有的竞争者不做区分
很多情况下,有些竞争者并不会修改共享资源(只读不写)
普通自旋锁总是会限制只有一个内核路径持有锁,而实际上这种限制是没有必要的
读写自旋锁的改进:
允许多个读者同时持有读锁(允许多个读者同时进入读临界区)
只允许一个写者同时持有写锁(只允许一个写者同时进入写临界区)
不允许读者和写者同时持有锁
与普通自旋锁相比,读写自旋锁更适合读者多,写者少的应用场景
相关API:
信号量
Linux内核中应用最广泛的同步方式: 自旋锁、信号量(semaphore)
自旋锁和信号量是一种互补的关系,它们有各自适用的场景
信号量可以是多值的,当其用作二值信号量时,类似于锁 : 一个值代表未锁,另一个值代表已锁
工作原理与自旋锁相反
获取锁的过程中,若不能立即得到锁,就会发生调度,转入睡眠
另外的内核执行路径释放锁时,唤醒等待该锁的执行路径
自旋锁的使用限制及信号量的解决方法
持有自旋锁的临界区不允许调度和睡眠 : 竟争激烈时整体性能不好
信号量解决了以上两个问题:
锁的竟争者不是忙等,信号量的临界区允许调度和睡眠而不会导致死锁
锁的竞争者会转入睡眠,从而让出CPU资源给别的内核执行路径,因此对锁的竞争不会影响整体性能
信号量的缺点:
中断上下文要求整体运行时间可预测(不能太长),而信号量临界区可能发生调度,因此不能用于中断上下文(只能用于进程上下文)
如果抢锁的过程很短,那么用信号量并不合算,因为进程睡眠加上唤醒的代价太大,消耗的CPU资源可能远远大于短时间的忙等
普通信号量
相关结构体:
count标识信号量的状态 : 值为0表示忙(已锁),值为正代表自由(未锁,允许竞争者进入临界区)
count的初值就是最大允许进入临界区的进程数目,初值为1的信号量就是二值信号量
二值信号量类似于一个普通的锁,而多值信号量类似于一个允许一定并发性的锁
wait_list字段是当信号量为忙时,所有等待信号量的进程列表,而lock则是保护wait_list的自旋锁
相关API:
读写信号量
读写信号量的引入原因类似于读写自旋锁,是为了区分不同的竟争者(读者和写者),以便允许读者共享而写者互斥
相关结构体:
主要字段 count、 wait_list 和 wait_lock的含义与普通信号量基本相同,owner指向写者进程
相关API:
互斥量
互斥量是二值信号量
相关结构体:
在数据结构上与信号量几乎相同
相关API:
互斥量提供了一套新的API,这套API专为二值的互斥量优化
死锁原理
典型例子:
spinlock, rwlock, mutex 在 linux内核中是不能递归,如果产生递归会发生死锁
例如: 调用spinlock(A),在本cpu上发生中断,中断上下文也调用到 spinlock(A)
多个CPU相互等待lock A/B
--------CPU1------------------------------CPU2---------
获取 lock A -> ok ----------------- 获取 lock B -> ok
获取 lock B -> spin --------------- 获取 lock A -> spin
Last updated
Was this helpful?