换个角度理解锁

隔了窗户纸

说到锁,各种书籍或者博客上嗖嗖嗖就能上来一堆关于锁的名词,比如读写锁、互斥锁、自旋锁,等等。然后介绍这些锁的特点、使用场景等,怎么看都有一种隔了窗户纸的感觉,感觉需要一个概念或者抽象重组,形成有组织有系统的全景图像。

那么怎么去抽象重组呢?不妨换个角度,结合操作系统的一些原理来看锁。

进程调度的注意点

粗略复习下操作系统的“进程调度”。

进程是什么、进程调度是什么,就不再重复了。

需要注意的知识点是:进程调度算法会把“缺乏资源”的进程置于休眠队列,又要把休眠队列中“资源要求可以满足”的进程置于等待队列,然后时间片一到就会调度它们。————看似平淡的一句话。

一般学到这里,都会觉得:所谓“资源”,常见的就是例如磁盘访问、网络通信之类东西。它们忙不忙,操作系统是知道的。

其实不然。

操作系统在这里做了抽象或者说归一化,它压根不知道进程在等什么。所谓的“资源”,在进程调度逻辑看来,就是一个个“锁”:请求锁得不到满足,就是“缺乏资源”;锁被释放了,请求它的进程就“有资源可用”了

例如有一些分析工具可以看出多少进程阻塞在磁盘访问上了,这个是从何而来的呢?
答案是:操作系统会维护一个“锁”的列表,找到这个锁的对应项,读它的相关信息,再找到申请它的进程队列,自然就知道哪些进程试图访问磁盘但暂时未得到满足了(注意这类锁并不需要进程显式申请,相关逻辑已经包含在对应的系统调用里了)。

换个角度看“锁”

有了这个前提,从这个角度再看锁,就会有新的理解:

  1. “自旋锁”是一种申请不到也不知会操作系统的锁。

    这可以避免进程因被识别为“资源不足”而被操作系统置入休眠队列,从而避免不必要的上下文切换开销。但缺点是,它会导致“申请不到锁时执行死循环”,也就是站着茅坑不拉屎。

    • 如果是单核单线程 CPU,它白白发一个时间片的热然后失去执行权(它占用了时间片,可能导致能释放资源给它的进/线程压根得不到执行机会);

    • 如果在多 CPU 或多核平台上,且这个锁一定会在远短于一个时间片的时间内被请求到,它才可能真正提高效率。

  2. 其它锁都是申请不到就通知操作系统:资源不足,没法干活,申请休息。

    于是操作系统暂停当前进/线程并将其置于等待队列,腾出它的 CPU 给其它进/线程使用;直到另外一个进/线程释放锁,它才可以再次得到执行机会。

再去看“资源”

进一步的,资源又分许多种:

有的资源同时只允许一个访问,无论读写;于是抽象它为互斥锁

有的资源同时只允许一个修改但可以允许许多个读取,于是抽象它为读写锁

此外,还有一类典型的资源访问场景,是“甲做某些先期准备、就绪后再由乙接续工作”(如果用普通锁在用户态写,要么会搞的很复杂,要么就得忙等)。使用条件变量,就可以让甲每取出一个任务就发一个通知,然后操作系统自动转发通知给乙(所谓“转发通知”,实质上是,乙执行“等待条件变量”的系统调用,然后因条件得不到满足而被操作系统自动挂起;等条件满足了,操作系统才会让其继续执行)。

结论

“锁”是一种抽象,或者说是一种手段,通过不同的锁,进程可以配合操作系统,达到既不浪费CPU时间、又把各种资源利用到极致的目的。

所以,锁的本质,并不是什么独占、互斥、等等类似概念,而是很简单的东西 ------ 资源。锁是对资源的抽象。

归根结底,还是为了效率。

马后炮

其实,操作系统很重要的目的之一,就是统筹各种资源,达到高效利用。所以应该一直带着这种意识去学习操作系统。

你可能感兴趣的:(换个角度理解锁)