本章将了解python的多线程机制和GIL,了解threading模块与thread模块的区别和关系,熟练掌握使用threading.Thread进行多线程执行。
什么是多线程?
引言
在多线程(Multithread)编程出现之前,电脑程序的运行由一个执行序列组成,执行序列按顺序在主机的CPU运行。即使子任务相互独立,互相无关时也是按照一条线的顺序执行。
所以多线程编程的目的就是并行的运行这些相互独立的子任务,大幅度的提高整个任务的效率。
多线程编程对于某些任务是非常理想的,这些任务通常具有以下特点:
- 本质是异步的,需要有多个并发事物。
- 各个事物的运行顺序可以是不确定的,随机的,不可预测的。
- 这些子任务可能需要计算出一个中间结果,最终合并为最后的结果。
- 通常,运算密集型的任务比较容易分隔成多个子任务,如果使用单线程处理,则要处理多个外部输入源就很难。
一个顺序执行的程序要从每个IO终端信道检查用户输入时,程序无论如何也不能在读取IO终端信道的时候阻塞。因为用户输入的到达是不确定的,阻塞会导致其他IO信息的数据不能被处理。所以顺序执行通常使用非阻塞IO,或带有计时器的阻塞IO。
进程与线程
计算机程序只不过是磁盘中可执行的二进制的数据,他们只有在被读取到内存中,被操作系统调用的时候才开始他们的生命周期。
进程使程序的一次执行。**每个进程都有自己独立的地址空间、内存、数据栈以及其他记录其运行轨迹的辅助数据。**操作系统管理在其上运行的所有进程,并为这些进程公平的分配时间。进程也可以通过fork和spawn来完成其他任务。不过各个进程之间相互独立,所以只能使用进程间通讯(IPC)的方式,而不能直接共享信息。
线程跟进程有些相似,不同的是,所有线程运行在同一个进程中,共享相同的运行环境。他们可以想象成是在进程中并行运行的迷你进程。线程有开始、顺序执行、结束三部分。它有一个自己的指令指针,记录自己运行到什么地方,线程的运行可能被抢占(中断),或者暂时的被挂起(也叫睡眠),让其他的线程运行,这叫做让步。
一个进程中的各个线程之间共享一片数据空间,所以线程之间可以更加方便的共享数据以及相互通信。
然而实际中,对于单核CPU,真正的并发是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU让出来(系统中断),让其他的线程运行。关于进程与线程更多的内容需要去了解《操作系统原理》。
Python中的多线程
全局解释器锁(GIL)
Python代码的执行由python虚拟机(解释器主循环)来控制。对于python虚拟机的访问由全局解释器锁(GIL)来控制,这个锁保证同一时刻只有一个线程在运行。
在调用外部代码(如C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止。对此感兴趣的可以查看Python/ceval.c文件。
当一个线程结束计算时,就算是退出。。线程可以调用thread.exit()或者使用python退出进程的标准方法,如sys.exit()结束线程。不过,你不可以直接kill一个线程。
在python中使用线程
通常有两种方式:
- thread模块:通常不推荐使用,最大原因是当主线程退出时,其他线程没有被清楚就退出了。
- threading模块:推荐使用,对线程支持更为完善。
这里先从thread模块开始学习。thread模块中,除了产生线程之外,thread模块也提供了基本的同步数据结构的锁对象。start_new_thread()函数是thread模块中的关键函数,其参数为:函数、函数的参数以及可选的关键字参数。这个函数会产生一个新线程来运行。
thread模块函数还有:
- allocate_lock():分配一个LockType类型的所对象。
- exit():让线程退出。
LockType类型对象方法:
- acquire(wait=None):尝试获取所对象。
- locked():如果获取了锁对象返回True,否则返回False。
- release():释放锁。
# thread.start_new_thread()使用例子
import thread
from time import ctime,sleep
def loop0():
print 'start loop 0 at: ',ctime()
sleep(4)
print 'loop 0 down at ',ctime()
def loop1():
print 'start loop 1 at: ',ctime()
sleep(2)
print 'loop 1 down at ',ctime()
def main():
print 'start at: ',ctime()
thread.start_new_thread(loop0,(