PDF 及笔记:
电脑上如果 PDF 不展示或者展示不正常,使用 Chrome 并安装 PDF Viewer 插件。其他情况请下载文件查看:tlpi-ch30。
部分笔记
Critical seciton(临界区)的定义:一段代码如果访问了线程间共享的资源、并且执行时需要是原子的(资源在其执行期间不被其他线程同时访问),这段 代码 则称为临界区。
即使像变量自增(glob++
)这样简单的操作,也不能保证是原子的。比如在 RISC 精简指令集下,自增也需要被拆分成几个指令执行。
Mutex
Mutex 是 mutual exclusion 的意思,中文翻译为「互斥锁」:
- 状态:locked, unlocked
- 动作:
- 加锁:lock / acquire
- 解锁:unlock / release
规则:
- 一个线程加锁后,就拥有这个锁,只有它能将其解锁
- 其他线程也尝试加这个锁时,会阻塞或者失败(视加锁方式而定)
- 锁是加在临界区(critical section)的,即是加在某段代码中,而这段代码中往往有访问共享资源的逻辑
- 但锁并不能强行保护共享资源。如果其他线程无视锁的存在,在访问共享资源前不先尝试加锁,那么它仍然可以访问该资源
使用 mutex 保护临界区:
看 PDF 代码示例会加深理解。
有额外的加锁方式:trylock()
及 timedlock()
。
Mutex 的性能好,大部分情况下是在 user space 做操作,没有系统调用带来的消耗。
使用 mutex 可能带来死锁。最简单的场景:线程 A 加了锁 1,准备加锁 2;线程 B 加了锁 2,准备加锁 1。两者会无限期的互相阻塞。
解决方法:
- 定义一套锁的层次结构,各线程加锁时都遵循一样的顺序
- 各线程使用
trylock()
,一旦无法加锁,就把之前加的锁全部释放,在一定时间间隔后再重试
有不同的 mutex 类型。它们针对这三种场景有不同的行为:
- 同个线程对同个 mutex 加锁多次
- 一个线程对一个它不拥有的锁做 unlock 操作
- 一个线程对一个还没被上锁的锁做 unlock 操作
三种不同的 mutex type 有不同的行为:
- NORMAL:没有死锁检测。线程加锁其已拥有的锁时,造成死锁。Unlock 其不拥有的锁或者未加锁的锁时,行为是未定义的(Linux 下不会报错)
- ERROR_CHECK:上述三种场景都会报错。这种 mutex 性能比 NORMAL 稍差,但适合用来调试
- RECURSIVE:一个线程不停对某个 mutex 加锁时,这种模式下系统会记录一下加锁计数。线程需要做跟其加锁次数一样多次的 unlock ,才能将一个 mutex 释放。对于其他两种场景,这种模式下会报错
SUSv3 规定默认的模式为 DEFAULT,但它对上述三种场景的行为故意没有定义。在 Linux 下,默认模式等同于 NORMAL。
Condition Variables
Condition variables 是一种线程间协作的机制。典型场景是:
- 多个线程共同操作、关心某些共享变量,它们可以通过 condvar 来传递信号
- 线程 1 修改完变量后,可以调用
pthread_cond_signal()
触发关心这些变量的其他进程醒来 - 其他进程通过
pthread_cond_wait()
收到信号醒来后,检查共享变量并执行相应的逻辑
看代码示例会加深理解。代码写起来有一些较难理解的部分,我在 PDF 中加了注释。