通过前面两节的学习,我们对线程有了基本认识。了解了线程和进程的区别以及线程的使用方法和场景。本节学习线程的状态和Thread类的主要方法。
1、线程的生命周期及状态
线程从创建到消亡,要经历创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、等待(waiting)、消亡(dead)等若干状态,其中运行、阻塞、等待状态之间会互相转换。
线程创建后,不会立即进入就绪状态,需要等待Python分配线程运行所必备的资源,如内存资源、运行栈、程序计数器等,所有资源分配完毕后,线程进入就绪状态。
进入就绪的线程,需要等待Python的调度以获取CPU的时间,对于单核CPU来说,当前时间段只能运行一个线程,如果是多核CPU,可以运行多个线程。线程得到CPU的执行时间后,就进入了运行状态。
进入运行状态的线程,可能会由于多种原因导致线程不能继续运行下去。例如,线程自身进入睡眠状态、或者被其它线程阻塞、或者被Python调度进入等待状态等。此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。
线程执行完毕,Python会清理线程所使用到的资源,线程进入消亡状态。
线程从创建到消亡状态之间的转换如下图所示:
图 1 线程从创建到消亡过程状态转换图
2、线程的上下文切换
前面说到线程需要轮换使用CPU的时间,当正在运行的线程被Python切换到另一个线程时,需要记录当前运行线程的运行栈、程序计数器等数据和线程状态,当线程再次被唤醒时,能够保持当前的状态继续执行。这种过程称为线程的上下文切换。
举个例子,假如线程A正在读取文件内容,读到一半时,Python需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,需要保持线程A读取文件的状态。
对于线程的上下文切换实际上就是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。
3、Thread类的主要方法
在“多线程的使用场景及方法”一节,我们已经掌握如何使用Thread类启动线程,下面重点讲述Thread类提供的主要方法。
● start ()
用于启动线程,需要注意的是,在某个线程上调用这个方法后,它还需要看Python是否调度到该线程,只有调度到该线程,它才运行。
● run ()
线程执行入口,当线程进入执行状态后,该方法被调用。继承的线程类需要重写该方法。
● join(timeout)
join方法主要用于协调子线程和主线程的同步。当一个进程或者说一个Python程序启动后,会默认创建一个主线程,主线程会创建多个子线程,但主线程和子线程的运行过程是并发的,这样可能就会出现主线程结束,子线程依然在运行的情况。子线程调用join方法后,就会被添加到与主线程的同步队列,当主线程任务结束后,如果在同步队列的子线程没有结束,主线程会进入阻塞状态,等待子线程的完成。
● getName()
获取当前线程的名称。
● setName (String)
用于设置当前线程名称。传入的参数为字符串类型。
● is_alive()
用于判断当前线程是否在活动状态。在run()方法开始之前,直到run()方法终止之后,此方法返回true。
● setDaemon (bool)
用于将该线程标记为守护线程。该方法必须在调用start()方法之前调用,否则将引发RuntimeError。传入的参数为布尔类型,True表示设置为守护线程。
如果设置一个子线程为守护线程,当进程或主线程执行完成后,不管子线程是否执行完毕,都会杀死子线程并退出程序。子线程创建时会继承父线程的daemon标志,也可以调用该方法显示设置daemon标志。
如果设置一个子线程为非守护线程,进程或主线程会等待该子线程执行完成后再退出。
● isDaemon()
用于判断当前线程是否是守护线程,守护线程返回True,否则返回False。
Thread类方法使用代码如下:
import threading
class MyThread(threading.Thread):
def __init__(self,incount):
threading.Thread.__init__(self)
self.count = incount
def run(self):
r1 = range(0,self.count)
for i in r1:
print("计数: %d" % (i))
def main():
t1 = MyThread(5)
#设置线程名称
t1.setName("计数器线程");
#输出线程状态
print("线程状态:%s" % (t1.is_alive()))
#设置t1为守护线程
t1.setDaemon(False);
#启动线程
t1.start();
# 输出线程状态
print("线程状态:%s" % (t1.is_alive()))
#输出线程名称
print("线程名称:%s" % (t1.getName()))
if __name__ == "__main__":
main()程序输出结果如下图所示:

图 2 Thread方法使用示例输出结果
一个线程从它的创建到消亡的过程,会经历不同的阶段。当创建Thread类或其子类的一个实例时,就意味者该线程处于新建状态,一旦调用了该线程实例的start方法,该线程便进入了运行状态。在线程的运行过程中,它不一定能始终保持运行的状态,可能会被阻塞进入等待状态、也可能会进入休眠状态,也可能会被再次唤醒进入运行状态,状态切换一直持续,直到线程消亡。