多线程技术

python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的,原始的线程以及一个简单的锁。threading基于Java的线程模型设计。

1.threading模块
应该避免使用thread模块,原因是它不支持守护线程。当主线程退出时,所有的子线程不管他们是否还在工作,都会被强行退出。有时候并不希望出现此种行为,因此就引入了守护线程的概念。threading模块支持守护线程,所以,我们直接使用threading来改进上述的例子。

#threads.py
#coding:utf-8 from time import sleep,ctime import threading #听音乐任务 def music(func,loop): for i in range(loop): print('i was listening to %s! %s' % (func,ctime())) sleep(2) #看电影任务 def movies(func,loop): for i in range(loop): print('i was watch the %s!%s' %(func,ctime())) sleep(5) #创建线程数组 threads=[] #创建线程t1,并添加到线程数组 t1=threading.Thread(target=music,args=(u'爱情买卖',2)) threads.append(t1) #创建线程t2,并添加到线程数组 t2=threading.Thread(target=movies,args=(u'阿凡达',2)) threads.append(t2) if __name__=="__main__": #启动线程 for t in threads: t.start() #守护线程 for t in threads: t.join() print ('all end %s' %ctime())

执行之后,结果如下图所示:

-import threading:引入线程模块
-threads=[]:创建线程数组,用于装载线程。
-threading.Thread():通过调用threading模块的Thread()方法来创建线程。

通过for循环遍历threads数组中所装载的线程,start()开始线程活动,join()等待线程终止。如果不使用join()方法对每个线程做等待终止,那么在线程运行的过程中可能会去执行最后的打印“all end…”
代码分析:
从上面的运行结果可以看出,两个子线程(music/movie)同时启动于46分09秒,直到所有线程结束于46分19秒,总耗时10秒,movie的两次电影循环共需要10秒,music的歌曲循环需要4秒,从执行结果看出两个线程达到了并行工作。

 

2.优化线程的创建
上述例子中,每创建一个线程都需要创建一个t(t1,t2,……),当创建的线程较多时,这样不方便,因此下面例子将作出改进。

#player.py
#coding:utf-8 from time import sleep,ctime import threading #创建超级播放器 def super_player(file_,time): for i in range(2): print 'Start playing:%s! %s' %(file_,ctime()) sleep(time) #播放的文件与播放时长 lists={u'爱情买卖.mp3':3,u'阿凡达.mp4':5,u'我和你.mp3':4} threads=[] files=range(len(lists)) #创建线程 for file_,time in lists.items(): t=threading.Thread(target=super_player,args=(file_,time)) threads.append(t) if __name__=="__main__": #启动线程 for t in files: threads[t].start() for t in files: threads[t].join() print 'all end"%s' %ctime()

执行之后,结果如下图所示:

 

 

此例中,对播放器的功能也做了增强,首先,创建了一个super_player()函数,这个函数可以接收播放文件和播放时长,可以播放任何文件。

然后,我们创建了一个lists字典用于存放播放文件名与时长,通过for循环读取字典,并调用super_player()函数创建字典,接着将创建的字典都追加到threads数组中。

 

3.创建线程类
除直接使用python所提供的线程类外,还可以根据需求自定义自己的线程类。

#mythread.py
#coding:utf-8 from time import sleep,ctime import threading #创建线程类 class MyThreads(threading.Thread): def __init__(self,func,args,name=''): threading.Thread.__init__(self) self.func=func self.args=args self.name=name def run(self): self.func(*self.args) def super_play(file_,time): for i in range(2): print('Start playing:%s! %s' %(file_,ctime())) sleep(time) lists={u'爱情买卖.mp3':3,u'阿凡达.mp4':5,u'我和你.mp3':4} threads=[] files=range(len(lists)) for file_,time in lists.items(): t=MyThreads(super_play,(file_,time),super_play.__name__) threads.append(t) if __name__=='__main__': #启动线程 for i in files: threads[i].start() for i in files: threads[i].join() print'all end:%s' %ctime()

执行之后,结果如下图所示:

 

 

MyThreads(threading.Thread)
创建MyThread类,用于继承threading.Thread类
__init__()类的初始化方法对func、args、name等参数进行初始化。


在python2中,apply(func[,args[,kwargs]])函数的作用是当函数参数已经存在于一个元组或字典中,apply()间接地调用函数。args是一个包含将要提供给函数的按位置传递的参数的元组。如果省略了args,则任何参数都不会被传递,kwargs是一个包含关键字参数的字典。

Python3中已经不再支持apply()函数,所以将
-apply(self.func,self.args)
修改为
Self.func(*self.args)

最后,线程的创建与启动与前面的例子相同,唯一的区别是创建线程使用的是MyThreads类,线程的入参形式也有所改变。