一直以来对于线程的理解是比较模糊的,之前在用php的时候,由于php是不直接支持多线程,要用php_threads的扩展。最近由于公司项目的后台需要要到多线程,故学习了一下python的多线程,下面通过一个简单的例子说明一下。
在实际中,比如说要听音乐,看电影,做其他的任务,按照单例的话,只能是一件做接着一件做。如果每件事都是很耗时的,那就会影响效率。同理在编程的世界里。同样有很多的相似的逻辑业务。比如群发邮件,按照单例我们的程序就会用循环的方式,发完一个再发一个。但是,发邮件的这个过程却是要耗时的,这样样子的浪费多长的时间才能将大量的邮件发完。而且程序还有一个超时时间限制呢!(虽然可以设置为无限制)。可想而知,这样子的话,就会造成效率低,甚至发送不成功。
下面进入主题
1、看一个简单的例子
#!D:\Python43\python.exe
# -*- coding: UTF-8 -*-
from time import ctime,sleep
#听音乐
def music(musicname):
for i in range(2) :
print("我正在听音乐叫%s,时间是:%s" %(musicname,ctime()))
sleep(5)
#看电影
def movie(moviename):
for j in range(2) :
print("我正在看电影叫%s,时间是:%s" %(moviename,ctime()))
sleep(2)
#主线程
if __name__ =='__main__':
music('当爱已成往事')
movie('中华英雄')
print('所有的娱乐结束,时间:%s' %ctime())
定义两个函数,分别做两个任务,听音乐和看电影
music函数就是任务打印出听音乐这句话(程序执行非常快,纳秒级),这个时间是在15:36:56.然后等待5s,再去打印出听音乐这句话,时间是15:37:01,然后再睡眠等待5s.所以在这个过程任务的用时为10s.
接着到执行movie函数,睡眠时间2s.这个过程用时就是4s
最后,执行主程序的结束。我的娱乐任务完成。所以在这个过程他是按照程序单线程一直执行的。效率非常低。
2、多线程引入(非阻塞)
</pre><pre name="code" class="html">def music(musicname):
for i in range(2) :
print("我正在听音乐叫%s,时间是:%s" %(musicname,ctime()))
sleep(2)
#看电影
def movie(moviename):
for j in range(2) :
print("我正在看电影叫%s,时间是:%s" %(moviename,ctime()))
sleep(2)
threads=[]
t1 = threading.Thread(target=music,args=(u'一路顺风',))
threads.append(t1)
t2 = threading.Thread(target=movie,args=(u'让子弹飞',))
threads.append(t2)
#主线程
if __name__ =='__main__':
#循环执行子线程
for t in threads :
#将父线程设置为了守护线程。
#根据setDaemon()方法的含义,
#父线程打印内容后便结束了,
#不管子线程是否执行完毕了。
t.setDaemon(True)
t.start()
print('所有的娱乐结束,时间:%s' %ctime())
可以看到,在执行主程序的时候,三个任务基本是同时完成,而且music和movie只执行一遍,任务还没全部完成。为什么呢?
首先过程是先把所有的子线程启动,即musi和movie,这里是两条不同路,接着同时执行主程序的
print('所有的娱乐结束,时间:%s'%ctime()),又是一条路
主程序结束,就会将退出,同时子线程也退出,不管你的任务有没有完成。所以music和movie在启动并打印出那句话时,在睡眠等待,那边的主程序也已经执行到打印操作,所以主程序就退出,同时也通知正在睡眠等待的子线程退出。所以他们只执行一次打印任务,就被迫退出。为什么会被迫退出呢?那与我们的逻辑业务不符合啊。不急,接着往下看。原因就在于这个setDaemon(True)就是将主进程设置为守护线程,根据setDaemon()方法的含义,父线程打印内容后便结束了,不管子线程是否执行完毕了,都退出
3、多线程(阻塞)
#!D:\Python43\python.exe
# -*- coding: UTF-8 -*-
from time import ctime,sleep
import threading
#听音乐
def music(musicname):
for i in range(2) :
print("我正在听音乐叫%s,时间是:%s" %(musicname,ctime()))
sleep(5)
#看电影
def movie(moviename):
for j in range(2) :
print("我正在看电影叫%s,时间是:%s" %(moviename,ctime()))
sleep(2)
threads=[]
t1 = threading.Thread(target=music,args=(u'一路顺风',))
threads.append(t1)
t2 = threading.Thread(target=movie,args=(u'让子弹飞',))
threads.append(t2)
#主线程
if __name__ =='__main__':
#循环执行子线程
for t in threads :
#将父线程设置为了守护线程。
#根据setDaemon()方法的含义,
#父线程打印内容后便结束了,
#不管子线程是否执行完毕了。
<span style="color:#ff0000;">#t.setDaemon(True)</span>
t.start()
<span style="color:#ff0000;">for t in threads :
t.join()</span>
print('所有的娱乐结束,时间:%s' %ctime())
将
t.setDaemon(True)</span>
屏蔽掉,同时再加入一个for循环
for t in threads :
<span style="white-space:pre"> </span>t.join()
在执行主程序时,启动两个子线程,在15:18:09 同时执行一遍打印任务,两个子线程在睡眠等待中,时间到了,有同时个字执行一片打印任务。再睡眠等待2s后,再执行主程序的打印任务。
其实起作用的就是这个join()函数,这是一个阻塞函数,要子线程执行完了才能继续往下执行主程序(或其他程序)。
所以在for循环中,join()阻塞先等待第一个子线程结束后,在继续等待第二个子线程结束,如果第二个或者后面提前比前面的结束,则就会下一个join(),不需要等待(因为join()检查到该线程已经结束)。循环之后,就会往下执行主程序。所以整个过程所用的时间就是用时最长的任务的时间。所有的几乎是同时进行。
所以我们可以知道,setDaemon是与join()相对的。功能上基本是相反的