线程与进程
进程是执行中的一个程序,每个进程都拥有自己的地址空间、内存、数据栈以及其它的辅助数据。而线程可以称为轻量级进程,位于进程内,共享上下文地址。
全局解释器锁
python通过引用计数进行内存管理,每个python对象都具有引用计数的变量。当对象的引用计数为0时,就释放该对象占用的内存空间。为了防止有多个线程同时对对象的引用计数进行更改,便引入了全局解释器锁。因此虽然python解释器支持多线程编程,但由于全局解释器锁的存在,在任意时刻,只有一个线程可以处于执行状态。这种情况会对计算繁忙型的程序产生较大的影响。
python中的线程模块
python在绝大多数类UNIX操作平台(如linux,mac osx)以及Windows上是支持多线程编程的。python中的多线程编程模块有thread(python3中是_thread)、threading以及Queue模块。
thread模块提供了基本的线程和锁定支持;threading模块提供了高级的线程管理(在实际使用中,尽量使用threading模块而不是thread模块,因为threading模块更加先进,有更好的线程控制,同步原语也比thread模块多,另外,thread模块对线程的何时退出没有进行控制,当主线程结束时,其它所有线程无论完成与否,都同时结束);使用 Queue 模块,用户可以创建一个队列数据结构,用于在多线程之间进行共享。
未使用多线程的代码
from time import sleep,ctime
def loop_0():
print("start loop 0 at:",ctime())
sleep(4)
print("loop 0 done at:",ctime())
def loop_1():
print('start loop 1 at:',ctime())
sleep(2)
print("loop 1 done at:", ctime())
def main():
print("starting at:",ctime())
loop_0()
loop_1()
print("all done at:",ctime())
if __name__ =="__main__":
main()
运行结果如下:
starting at: Sun Oct 27 08:25:58 2019
start loop 0 at: Sun Oct 27 08:25:58 2019
loop 0 done at: Sun Oct 27 08:26:02 2019
start loop 1 at: Sun Oct 27 08:26:02 2019
loop 1 done at: Sun Oct 27 08:26:04 2019
all done at: Sun Oct 27 08:26:04 2019
可以看出,在不使用多线程的情况下,程序是依次执行的,总共耗时6秒。
使用thread多线程
import _thread
from time import sleep,ctime
def loop_0():
print("start loop 0 at:",ctime())
sleep(4)
print("loop 0 done at:",ctime())
def loop_1():
print('start loop 1 at:',ctime())
sleep(2)
print("loop 1 done at:", ctime())
def main():
print("starting at:",ctime())
_thread.start_new_thread(loop_0,())
_thread.start_new_thread(loop_1,())
sleep(4) #根据thread的特性,主线程结束时会直接强制结束其它线程,因此设置了休眠时间,自己可以试试把该语句注释掉会是什么结果
print("all done at:",ctime())
if __name__ =="__main__":
main()
运行结果如下所示
starting at: Sun Oct 27 08:28:08 2019
start loop 1 at: Sun Oct 27 08:28:08 2019
start loop 0 at: Sun Oct 27 08:28:08 2019
loop 1 done at: Sun Oct 27 08:28:10 2019
all done at: Sun Oct 27 08:28:12 2019
loop 0 done at: Sun Oct 27 08:28:12 2019
多线程的执行时间为4秒,比单线程的执行时间少。由于注释中提到的thread的特性,为了保证程序正确执行,需要确保在主线程执行结束时,所有其它子线程均执行结束。因此,在使用thread模块进行多线程编程时,需要额外编写语句去确认其它子线程是否执行完毕。
使用threading多线程
threading模块支持守护线程,如果把一个线程设置为守护线程,就说明该线程是不重要的,主线程结束时不需等待该线程执行结束。同样的,只有当其它非守护线程执行结束时,主线程才能结束。
同样的多线程例子,使用threading模块的代码如下所示。
import threading
from time import sleep,ctime
loops = [4,2]
def loop(nloop,nsec):
print("start loop {} at:{}".format(nloop,ctime()))
sleep(nsec)
print("loop {} done at:{}".format(nloop,ctime()))
def main():
print('starting at :',ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=loop,args=(i,loops[i]))
threads.append(t)
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
print("all done at:",ctime())
if __name__ == '__main__':
main()
运行结果如下
starting at : Sun Oct 27 09:03:40 2019
start loop 0 at:Sun Oct 27 09:03:40 2019
start loop 1 at:Sun Oct 27 09:03:40 2019
loop 1 done at:Sun Oct 27 09:03:42 2019
loop 0 done at:Sun Oct 27 09:03:44 2019all done at:
Sun Oct 27 09:03:44 2019
该方法先实例化Thread对象,并将函数和参数传递进去,然后得到实例化对象,得到的对象并不会立即开始执行,而是在调用start之后才开始执行。join()方法将等待线程结束,或者在提供了超时时间的情况下,达到超时时间才会执行完毕。