一 概述
二 类型
三 语句
四 函数
五 数据
六 内存
七 代码
附录
7. 线程基础
线程(thread)有时被称做轻量级进程(lightweight process, LWP)。
线程是程序执行流的最小单元,由 PID、寄存器和堆栈组成。进程有一到多个线程,共享程序内存空间及进程级资源(open files、signal 等)。
私有空间
- 执行栈(stack),存储函数调用过程信息(frame, call stack)。
- 局部存储(thread local storage, TLS)。
- 寄存器(register)。
$ ulimit -a stack size (kbytes, -s) 8192
注意:ulimit 分 soft 和 hard 两种。hard 是 soft 的上限,由 root 设定。而 soft 可以由用户更改。也可通过
getrlimit
、setrlimit
函数获取或设置。
线程状态
- 运行(running)。
- 就绪(ready):可立即运行,但 CPU 被占用。
- 等待(waitting):等待事件(如 I/O 或同步)发生,无法执行。
+--------------- 本线程被选中 ----------------+ | | v | +---------+ +-------+ | running | --------- 时间片用尽 ------------> | ready | +---------+ +-------+ | ^ | +----------+ | +----- 开始 ---> | waitting | --- 结束 ------+ +----------+
执行调度
被选中的线程拥有一段可执行时间,该段时间被称做时间片(time slice)。一旦时间耗尽,则被操作系统暂停,重新进入就绪状态,等待下次再被选中。
内核按系统线程分配时间片。线程是调度单位,进程是资源单位。理论上多线程可以抢夺更多时间片,但过多线程会导致更多上下文切换,反而降低效率。同一进程内的线程之间,无法分享时间片。
$ cat /proc/sys/kernel/sched_rr_timeslice_ms 100 $ sysctl kernel.sched_rr_timeslice_ms kernel.sched_rr_timeslice_ms = 100
常见线程调度有优先级(priority schedule)和轮转法(round robin)。高优先级的线程会更早执行,直到没有高优先级,低优先级才被调度。但操作系统会自动调整优先级,以使得调度更有效率。
根据执行方式,可分为 IO 密集型(IO bound)和 CPU 密集型(CPU bound)两类。IO 密集型更受欢迎,更容易被操作系统提升优先级,以便及时处理事件反馈。
使用 nice 可以影响进程优先级,进而影响到时间片大小。也就是 nice 值越高,抢占能力越弱,优先级越低。优先级
PRI += NI
,值越小优先级越高。NI[-20, 19]
。
$ nice -n 19 command $ renice -n 19 -p {pid}
过低的优先级,可能导致线程饿死(starvation)。也就是说,在它执行之前,总有高优先级线程试图执行。为避免这类情况,操作系统会提升长时间等待线程的优先级。
- 用户指定优先级。
- 根据等待状态频繁程度提升或降低优先级。
- 长时间得不到执行被提升优先级。
上下文
将 CPU 从一个线程切换到另一个线程,需要保存其状态(register),并恢复另一线程状态,这就是上下文切换(context switch)。IP/PC 寄存器被修改后,切换完成,开始执行新线程代码。
进程和线程都有上下文切换。区别在于线程切换期间,虚拟内存状态(TLB)保持不变。
向操作系统移交控制权,保存状态(register, MMU/TLB, ...),可能导致高速缓存失效(L1/2/3 cache)。
$ watch grep "ctxt" /proc/{pid}/status # 定时输出 voluntary_ctxt_switches: 3508 # 自愿(比如有较多 IO 等待操作) nonvoluntary_ctxt_switches: 64 # 强制(时间片用完)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论