深入了解Python的threading模块:守护线程的工作方式与设置
[[id_1355216692]d_197[id_1389638068]370599]id_1008784747]

若不存在客户端请求,守护线程便处于空闲状态。当将一个线程设为守护线程时,这意味着该线程并不重要,在进程退出时无需等待此线程执行完毕 。
当主线程准备退出时,若不需要等待某些子线程完成,便可为这些子线程设置守护线程标记。当该标记值为真时,表明线程不重要,也就是说该线程仅用于等待客户端请求,不做其他任何事情。
使用语句thread.daemon=True能够将一个线程设置为守护线程,同样可通过这个值查看线程的守护状态,一个新的子线程会继承父线程的守护标记,整个python程序(即主线程)会在所有非守护线程退出后才退出。
threading模块包含许多好用的同步机制,这其中不只有Thread类,还有其他的
其中,Thread类属于threading模块,它是主要的执行对象。
下面是Thread类的属性和方法列表:
使用Thread类,可以有多种方法创建线程:
通常情况下,我们会采用第一种方法或者第三种方法。要是需要一个更契合面向对象的接口,那么会倾向于选择第三种方法。不然的话,就采用第一种方法 。
第一种方法:创建Thread类,传递一个函数
在下面的脚本里,我们首先实例化Thread类,接着传递一个函数以及其参数,当线程开始执行时,该函数也会随之被执行:
'''
遇到问题却没有人来解答吗 小编创建了一个QQ群 群号是778463939 用于Python学习交流
寻找有相同志向的小伙伴,大家相互帮助,群里还有质量不错的视频学习教程,以及PDF格式的电子书!
'''
import threading
from time import sleep,ctime
不再将4秒和2秒硬性编码到不同函数里,而是运用唯一的loop()函数,把这些常量放入列表loops中。
loops=[4,2]
def loop(nloop,nsec):
print('开始循环',nloop,'at:',ctime())
sleep(nsec)
print('循环',nloop,'结束于:',ctime())
def main():
print('程序开始于:',ctime())
threads=[]
nloops=range(len(loops))
for i in nloops:
t=threading.Thread(target=loop,args=(i,loops[i]))
进行循环,实例化2个Thread类,传递函数以及其参数,把线程对象放置到一个列表里面。
threads.append(t)
for i in nloops:
threads[i].start() #循环 开始线程
for i in nloops:
threads[i].join() 循环使用join()方法,能够使主线程等候所有线程都完成执行。
print('任务完成于:',ctime())
main()
和thread模块相比,不同点在于,实现同样的效果时,thread模块需要锁对象,而threading模块的Thread类不需要。实例化Thread(调用Thread())和调用thread.start_new_thread()有最大区别,那就是新线程不会立即执行!这是一个极为有用的同步功能,特别是在我们不期望线程马上开始执行的情况下。
当所有线程分配完毕,通过调用每个线程的start()方法使其开始运行。相较于thread模块管理一组锁(进行分配、获取、释放并检查锁状态),threading模块的Thread类只需为每个线程调用join()方法就行。join(timeout=None)方法会等待线程结束,或者是当达到指定的timeout时间时。这种锁又被称作自旋锁。
最重要的是:join()方法,实际上你根本没必要调用它。一旦线程启动,便会持续执行,直至给定的函数完成后才退出。要是主线程还有别的事情要做(无需等待这些线程完成),那么可以不调用join()。join()唯有在你需要等待线程完成之际才会有用。
例如上面的脚本中,我们注释掉join()代码:
import threading
from time import sleep,ctime
#不再把4秒和2秒硬性的编码到不同的函数中,而是使用唯一的loop()函数,并把这些常量放进列表loops中
loops=[4,2]
def loop(nloop,nsec):
print('开始循环',nloop,'at:',ctime())
sleep(nsec)
print('循环',nloop,'结束于:',ctime())
def main():
print('程序开始于:',ctime())
threads=[]
nloops=range(len(loops))
for i in nloops:
t=threading.Thread(target=loop,args=(i,loops[i])) #循环 实例化2个Thread类,传递函数及其参数,并将线程对象放入一个列表中
threads.append(t)
for i in nloops:
threads[i].start() #循环 开始线程
#for i in nloops:
#threads[i].join(),通过循环join()方法,能使主线程等待,直至所有线程都执行完毕。
print('任务完成于:',ctime())
main()
我们发现,主线程的任务会先执行,其任务完成于两个循环线程之前,且在循环X结束的时候完成 。
第二种方法:创建Thread类的实例,传递一个可以被调用的类实例。
创建线程时,有一种类似传入函数的方法,即传入一个可调用的类的实例,该实例用于线程执行,这种方法更接近面向对象的多线程编程。与传入一个函数或从一个函数组中选择相比,这种可调用的类包含一个执行环境,具有更好的灵活性。
在上述的mtsleepC.py脚本里添加一个新类ThreadFunc,对其进行稍微的改动,进而形成mtsleepD.py文件:
'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
#!/usr/bin/env python
import threading
from time import sleep,ctime
loops=[4,2]
class ThreadFunc(object):
def __init__(self,func,args,name=''):
self.name=name
self.func = func
self.args=args
def __call__(self):
self.func(*self.args)
def loop(nloop,nsec):
print('开始循环',nloop,'在:',ctime())
sleep(nsec)
print('结束循环',nloop,'于:',ctime())
def main():
print('程序开始于:',ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__)) #传递一个可调用类的实例
threads.append(t)
for i in nloops:
threads[i].start() #开始所有的线程
for i in nloops:
threads[i].join() #等待所有的线程执行完毕
print('任务完成于:',ctime())
main()
在上述脚本里,主要添加了ThreadFunc类,在实例化Thread对象的时候,以传参的方式同时实例化了可调用类ThreadFunc,这里同时实现了两个实例化。
我们研究创建ThreadFunc类的思想,我们希望这个类更具通用性,不局限于loop()函数,为此添加了一些新内容,比如该类保存了函数自身、函数的参数以及函数名,构造函数__init__()用于设定上述值。创建新线程时,Thread类的代码会调用ThreadFunc对象,这时会调用__call__()这个特殊方法。
第三种方法:派生Thread的子类,并创建子类的实例
和方法二相较,方法三在创建线程时使用子类,这相对而言更容易阅读,下面是mtsleepE.py脚本:
#!/usr/bin/env pyhton
import threading
from time import sleep,ctime
loops=[4,2]
class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args
def run(self):
self.func(*self.args)
def loop(nloop,nsec):
print('开始循环',nloop,'在:',ctime())
sleep(nsec)
print('结束循环',nloop,'于:',ctime())
def main():
print('程序开始于:',ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = MyThread(loop,(i,loops[i]),loop.__name__)
threads.append(t)
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
print('所有的任务完成于:',ctime())
main()
比较方法二和方法三,重要的变化在于,MyThread子类的构造函数要先调用其父类的构造函数,要重写run()方法,以此代替方法二中的__call__()方法。
优化第三种方法:
修改MyThread类,增加一些调试信息的输出,除简单调用函数外,将结果保存在实例的属性self.res中,增加新的方法getResult()来获取这个值。
以下代码对递归求斐波那契、阶乘和累计函数的执行情况进行了比较,它分别以单线程和多线程的方式来执行同样的任务:
'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
#!/usr/bin/env python
import threading
from time import ctime,sleep
#对MyThread进行封装
class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.func = func
self.name = name
self.args = args
def run(self):
print('开始执行',self.name,' 在:',ctime())
self.res = self.func(*self.args)
print(self.name,'结束于:',ctime())
def getResult(self):
return self.res
#斐波那契
def fib(x):
sleep(0.005)
if x < 2:
return 1
return fib(x-1)+fib(x-2)
#阶乘
def fac(x):
sleep(0.1)
if x < 2:
return 1
return x*fac(x-1)
#累加
def sum(x):
sleep(0.1)
if x < 2 :
return 1
return x + sum(x-1)
funcs=[fib,fac,sum]
n = 12
def main():
nfuncs = range(len(funcs))
#单线程
print('单线程模式')
for i in nfuncs:
print('开始',funcs[i].__name__,' 在:',ctime())
print(funcs[i](n))
print(funcs[i].__name__,'结束于:',ctime())
#多线程
print('多线程模式')
threads = []
for i in nfuncs :
t = MyThread(funcs[i],(n,),funcs[i].__name__)
threads.append(t)
for i in nfuncs:
threads[i].start()
for i in nfuncs:
threads[i].join()
print(threads[i].getResult())
print('所有的任务结束')
main()
总结
在程序里,为了能看到多线程对性能的改善情况,我们添加了sleep函数,以此来降低执行速度。
在单线程模式下,只是简单地依次调用每个函数,并且在函数执行结束时立即显示相关结果。而在使用多线程时,不会立刻显示结果,因为我们期望MyThread类具有很高的通用性,即无论有无输出都能执行。所以,我们会一直等待,直到所有线程都调用了join方法,之后再调用getResult()方法来显示每个函数的返回值。