前言
这将是一个系列,一个关于进程、线程和 协程的系列。
主要用于:回顾和复习以往所学的知识 以及 希望这点经验能够帮助正在学习编程的你。
最初的几章会讲到一些相关的计算机理论知识,可能相对枯燥。但这是基础,是理解后面程序必备的概念。如果有疑惑,可以加微信讨论,咱们一起进步。
线程的定义
线程(thread)是操作系统能够进行运算的最小单位。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。
线程的实现方式
根据管理线程所发生的位置,我们将线程分为以下三类:
- 内核支持线程内核线程驻留在内核空间,它们是内核对象。有了内核线程,每个用户线程被映射或绑定到一个内核线程。用户线程在其生命期内都会绑定到该内核线程。一旦用户线程终止,两个线程都将离开系统。这被称作”一对一”线程映射。优点是调度灵活,缺点是内核线程在用户态和内核态之间进行上下文切换,资源开销大。
- 用户级线程内核对线程包一无所知。从内核角度考虑,就是按正常的方式管理,即单线程进程(存在运行时系统)。也就是说,如果因为进程中一个线程发生阻塞(如IO资源引起的阻塞),那么其他线程也将得不到执行。
- 组合方式线程(posix线程调度模型,又称posix规范)posix线程调度是一个混合模型,很灵活,足以在标准的特定实现中支持用户级和内核级的线程。模型中包括两级调度–线程及和内核实体级。线程级与用户级线程类似,内核实体由内核调度。由线程库来决定它需要多少内核实体,以及他们是如何映射的。
上下文切换
- 什么是上下文切换?当下计算机系统中,CPU运行的线程数量远超过CPU的核数,而用户却感觉这些程序在同时运行。这得益于分时系统的设计理念——人们将CPU的时间划分为若干小段。每个线程只能在其中有限个小段上执行。待时间一到,CPU就会将线程挂起,使其进入就绪状态,而CPU转而执行下一个线程,只不过这个切换的过程对于用户来说太快,感知不到而已。而在这一个过程中,CPU需要将当前线程的工作环境保存起来(迁出工作区),然后将即将执行的线程的数据载入工作区,这一过程我们称之为上下文切换。拓展,上面说的是典型的被动上下文切换,又称之为飞自愿性上下文切换。除此之外,还有因当前进程资源不足发生的自愿性上下文切换,以及,中断信号导致的中断上下文切换。
- 上下文切换的成本我在控制台查看一下自己系统中上下文切换的次数。(满载状态下)
- $ sar -w 1 10
- Linux 4.15.0-153-generic (ubuntu-bionic) 08/09/21 _x86_64_ (2 CPU)
- 15:26:02 proc/s cswch/s
- 15:26:03 0.00 65892.00
- 15:26:04 0.00 248868.00
- 15:26:05 0.00 368564.00
- 15:26:06 0.00 391488.00
- 15:26:07 0.00 349058.00
- 15:26:08 0.00 353089.00
- 15:26:09 0.00 381352.00
- 15:26:10 0.00 379659.00
- 15:26:11 0.00 374822.00
- 15:26:12 0.00 129233.00
- Average: 0.00 304202.50
- # proc/s 每秒创建的任务总数。
- # cswch/s 每秒上下文切换的总数
我这是2核CPU,据统计目前主流的CPU每次上下文切换要花掉2~5微秒。我们取中位数3微秒。我们来计算一下每秒单核CPU花在上下文切换中的时间:3*304202/2/1000=0.456s。可以看出,频繁的上下文切换对性能的影响是巨大的。
由于同一个进程中的多个线程是共享进程资源的,所以从这个角度我们又把上下文切换分成两类:
- 进程切换:不同进程的线程之间切换。
- 线程切换:统一进程中不同线程之间的切换。
上图绿色的部分代表需要切换的上下文,我们可以看出,线程切换的开销远小于进程切换。
今天线程的理论只是就讲到这里,接下来我们将以Python为主要编程语言,带大家进行 多线程实践与性能分析,相对于干燥的理论知识,将会变得比较有趣。