早在 20 世纪 60 年代,进程的概念就出现了。但进程是资源的拥有者,进程的创建、销毁、调度代价都比较高,所以出现了对轻量级进程的需求。
到了 20 世纪 80 年代,出现了线程,类似于进程,线程可以独立运行,但使用的代价比进程小很多。
线程一般包括程序、数据和线程控制块,其中线程控制块包括以下信息:
线程是独立调度和分派的基本单位,线程切换消耗的资源比较少。在多核系统中,多线程可以完全并发执行,这样可以完全发挥硬件的计算能力。同一个进程内的线程共享很多资源,如文件、定时器等,所以线程通信是很方便的。
线程可以处于不同的状态,随着时间推移,线程在不同状态之间迁移。线程有 5 种状态,分别是新建、就绪、运行、阻塞和结束。
图 1 描述的便是线程在这些状态之间的转换。
最开始是创建一个线程,这个新创建的线程就处于“新建”状态,这是所有线程的起点。线程在该状态是可以进行各种配置的,如入口函数、栈的大小等。
操作 1 表示应用程序启动了该线程,这时线程将进入到“就绪”状态。处于就绪状态的线程还没有真正运行,但是已经满足了运行的全部条件。
操作 2 表示操作系统给其分配了处理器资源,现在线程就开始执行了,线程切换到新的“运行”状态。
操作 3 表示线程运行时碰到了问题,暂时不能继续执行下去了,如要读取文件数据,但该数据目前还没有捣入到系统,这时操作系统会将该线程调度出来并进入到“阻塞”队列中。这时该线程分配的处理器资源便可以释放出来并分配给其他“就绪”状态的线程使用。
操作 4 表示线程需要的资源已经就绪,现在该线程可以运行了。
操作 5 表示某个正在运行的线程被操作系统调度到就绪队列。由于处理器资源有限,处于“就绪”状态的线程可能数目远远大于处理器的个数,因此任意时刻只能一部分可运行的线程在运行。但也不能一直让某些线程运行而让其他线程处于等待状态,这是不合理的。所以操作系统会定时将一些运行中的线程调度处理,这样空闲出来的处理器就可以给其他等待中的线程使用。
操作 6 表示线程自行退出或者被强制退出。自行退出就是线程代码执行完成,这种方式比较优雅;强制退出就是外部代码强制要求线程停止运行,如主线程退出了,那么所有该进程内的线程都会被强制退出。强制退出可能导致某些运行结果没有来得及保存而丢失。自行退出就类似于关机,强制退出则类似于断电。
操作 7 就是强制退出。
在一个进程中,第一个启动的线程就是主线程,其具有如下特点:
我们常说的一个进程至少包含一个线程,这个至少包含的线程就是主线程。
另外,在后面的章节中还会经常提到几个线程相关的概念,这里先简要介绍一下这些概念。
Python 语言自然支持线程,不必另行安装特殊的软件包。
Python 系统自带了两个线程相关的包,一个是 thread,其要求用户提供一个函数作为线程的入口函数;另外一个包是 threading,其要求的输入是一个派生自 threading.Thread 类的实例对象。这两个包只是提供的接口函数不同,其内部的线程概念是一样的,后面分别对它们进行介绍。