线程的就绪状态:当位于新建状态的线程调用 start() 方法后,该线程就转换到就绪状态。所谓就绪,就是告诉 CPU,该线程已经可以执行了,但是具体什么时候执行,取决于 CPU 什么时候调度它。换句话说,如果一个线程处于就绪状态,只能说明此线程已经做好了准备,随时等待 CPU 调度执行,并不是说执行了 start() 方法此线程就会立即被执行。
线程的运行状态:当位于就绪状态的线程得到了 CPU,并开始执行 target 参数执行的目标函数或者 run() 方法,就表明当前线程处于运行状态。但如果当前有多个线程处于就绪状态(等待 CPU 调度)时,处于运行状态的线程将无法一直霸占 CPU 资源,为了使其它线程也有执行的机会,CPU 会在一定时间内强制当前运行的线程让出 CPU 资源,以供其他线程使用。**
线程的阻塞状态:当 CPU 对多个线程进行调度时,对于获得 CPU 调度却没有执行完毕的线程,就会进入阻塞状态。目前几乎所有的桌面和服务器操作系统,都采用的是抢占式优先级调度策略。即** CPU 会给每一个就绪线程一段固定时间来处理任务,当该时间用完后,系统就会阻止该线程继续使用 CPU 资源,让其他线程获得执行的机会**。对于具体选择那个线程上 CPU,不同的平台采用不同的算法,比如先进先出算法(FIFO)、时间片轮转算法、优先级算法等,每种算法各有优缺点,适用于不同的场景。
线程死亡状态:对于获得 CPU 调度却未执行完毕的线程,它会转入阻塞状态,待条件成熟之后继续转入就绪状态,重复争取 CPU 资源,直到其执行结束。执行结束的线程将处于死亡状态。线程执行结束,除了正常执行结束外,如果程序执行过程发生异常(Exception)或者错误(Error),线程也会进入死亡状态。
GIL,全称是 Global Interpreter Lock,也叫做全局解释器锁。对于 CPython,所有的 Python 线程都需要在解释器这个虚拟机中运行,而在运行之前都要先获取 GIL 这个锁,然后每执行 100 个字节码,解释器就自动释放 GIL 锁,让别的线程有机会执行。因此即使你有多个 CPU 核,多个线程在同一个 Python 虚拟机中也应该是交替执行的。 这就意味着:同一时间,只能有一个线程在执行的状态。GIL 对单线程程序没有影响,但会成为 CPU 密集和多线程代码的性能瓶颈。即使在多线程结构的代码中,在同一时刻 GIL 也只允许一个线程在执行状态,因此,GIL 成为了 Python 不受欢迎的一个特性。
CPU 密集型程序是指程序运行过程中 CPU 是性能瓶颈,该类程序会涉及大量数学计算,例如矩阵乘法/搜索/图像处理等。I/O 密集型程序是指程序花费了大部分时间来等待 I/O 事件,I/O 事件可能来自用户/文件/数据库/网络等。在从数据源获取到文件之前,I/O 密集型程序需要等待大量的时间;因为在 I/O 事件就绪前,数据源需要进行自己的处理过程。例如:用户花费时间思考向输入提示符输入什么内容(会占用时间),或者数据库在接收到检索请求后运行自己的程序(会占用时间)。
解决 GIL 的方法包括使用多进程、使用其他 Python 解释器或使用 C 扩展模块等。
2.4 生产者-消费者模型