现代操作系统大多都是多任务的,可以同时执行多个程序。进程是应用程序正在执行的实体,当程序执行时,也就创建了一个主线程。进程在创建和执行时需要一定的资源,比如内存、文件、I/O设备等。大多现代操作系统中支持多线程和进程。线程是CPU使用的基本单元,由主线程来创建,并使用这个进程的资源,因此线程创建成本低而且可以实现并行处理,充分利用CPU。
15.1 线程
Python3对多线程支持的是threading模块,应用这个模块,可以创建多线程程序,并且在多线程间进行同步和通信。在Python3中,可以通过以两种方式来创建线程。即通过threading.Thread直接在线程中运行函数;通过继承threading.Thread类来创建线程。
15.1.1 用threading.Thread直接在线程中运行函数
threading.Thread的基本使用方法如下:
15.1.2 通过继承threading.Thread类来创建线程
这种方法只要重载threading.Thread类的run方法,然后调用类的start()就能够创建线程,并运行run()函数中的代码。
【实例15-2】 预定义了一个函数,并以线程方式来运行,代码如下:
注意:继承threading.Thread类的子类中,如果需要重载__init__()方法,必须首先调用父类的__init__()方法。否则会引发AttributeError异常。
15.1.3 线程类Thread使用
join()方法作用是当某个线程或函数执行时需等另一个线程完成操作后才能继续,则应调用另一个线程的join()方法;其中的可选参数timeout用于指定线程运行的最长时间。isAlive()方法用于查看线程是否运行。
name属性是线程设置的线程名;daemon属性用来设置线程是否随主线程退出而退出,一般来说,其属性值为True时不会随主线程退出而退出。
注意:tb线程初始化只能在ta线程初始化之后,因为没有初始化的线程是不能调用join()方法。
注意: 如果在交互式模式下运行该实例,则还是会有输出。因为,在交互式模式的主线程只有在退出Python时才终止。
当一个进程拥有了多个线程之后,如果它们各做各的任务没有关系还行,可是既然同属于一个进程,它们之间总是具有一定关系的。比如多个线程都要对某个数据进行修改,则可能会出现不可预料的结果。为了保证操作正确,就要对多个线程进行同步。再如,多个线程之间还会需要进行通信。现在就来了解线程的同步和通信。
Python中可以使用threading模块中的对象Lock和RLock(可重入锁)进行简单的线程同步。对于同一时刻只允许一个线程操作的数据对象,可以把操作过程放在Lock和RLock的acquire方法和release方法之间。RLock可以在同一调用链中多次请求而不会锁死,Lock则会锁死。基本使用方法如下:
注意: 这一对方法若前一个调用n次,后一个也要调用n次,锁才能真正地释放。
15.2 进程
Python虽然支持多线程应用程序的创建,但是Python解释器使用了内部的全局解释器锁定(GIL),在任意指定的时刻只允许单个线程执行,并限制了Python程序只能在一个处理器上运行。而现代CPU已经以多核为主,但Python的多线程程序无法使用。使用Python的多进程模块可以将工作分派给不受锁定限制的单独子进程。
Python3对多进程支持的是multiprocessing模块和subprocess模块。使用multiprocessing模块创建和使用多进程,基本上和threading模块的使用方法是一致的。创建进程使用multiprocessing.Process对象来完成,和threading.Thread一样,可以用它以进程方式运行函数,也可以通过继承它来并重载run()方法来创建进程。multiprocessing模块同样具有threading模块中用于同步的Lock、RLock及用于通信的Event,本章不再赘述。
15.2.1 进程基础
注意: 子进程中运行的程序应该在搜索路径中,否则会找不到运行的程序而引发异常。
15.2.2 用Popen类创建进程
上节中所述的几个函数,其本质上都是通过Popen类来实现的简单版本的进程创建函数。直接使用Popen类不仅可以以新进程方式运行命令,还可以对其输入流和输出流等进行更多控制。
【代码说明】程序中首先用Popen产生一个子进程,用来执行保存在protest8.py中的python源代码(该源代码要求用户输入一串字符并直接输出和输出另一串写定的字符,以及打印一个未定义的变量a),然后调用Popen对象的communicate()方法向子进程中传入要输入的字符串,输出子进程的pid,最后分别输出子进程的标准输出和错误信息。
【运行效果】如图15.7所示,子进程的PID为4788(呵呵,是我朋友的手机尾号呢!),在输出(STDOUT)中输出了通过communicate()方法向子进程传送的输入These strings are from stdin.,而错误信息中输出了子进程的运行错误提示:NameError。
15.3 小结
本章主要介绍了线程和进程的基本知识、Python标准库中的线程和进程相关模块的基本使用。通过学习本章,你应重点掌握Python标准库中threading模块和subprocess模块,并能使用它们来创建多线程或多进程的Python程序,但也要注意,由于受到Python语言的全局锁的影响,其多线程只是建立在单CPU的基础上运行,并不能充分使用现代计算机中系统中的多CPU,这也是Python的多线程的局限性所在。