python多线程threading模块剖析Word格式文档下载.docx
- 文档编号:17402884
- 上传时间:2022-12-01
- 格式:DOCX
- 页数:15
- 大小:22.26KB
python多线程threading模块剖析Word格式文档下载.docx
《python多线程threading模块剖析Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《python多线程threading模块剖析Word格式文档下载.docx(15页珍藏版)》请在冰豆网上搜索。
,nloop,'
于'
ctime()
sleep(nsec)
loop函数'
完成于'
defmain():
开始主线程:
'
threads=[]#一个用于储存线程对象的列表
nloops=range(len(loops))
foriinnloops:
t=threading.Thread(target=loop,args=(i,loops[i]))#每次循环创建一个Thread的实例
threads.append(t)#将新创建的对象放到一个列表中
threads[i].start()#每次循环运行一个线程
threads[i].join()#等待子线程的完成
主线程完成:
if__name__=='
__main__'
:
main()
输出:
首先我们来看看如何创建一个Thread的实例,先看看其构造函数接受哪些参数:
__init__(self,group=None,target=None,name=None,args=(),kwargs=None,verbose=None)
self:
实例本身,特定格式,这里忽略。
group:
应该是None,这里是留给未来的扩展,当ThreadGroup类实现的时候。
我们不用理它。
target:
一个可调用的对象,可以是一个函数,也可以是一个类。
如果是一个类的时候,将调用类内部的__call__方法。
name:
用字符串表示线程的名字。
在默认的情况下,遵从"
Thread-N"
的格式,其中N是一个十进制的数字,从1开始。
可以为多个线程设定一样的名字。
args:
是一个元祖,以位置参数的方式传给被调用对象。
kwargs:
是一个字典,以关键字参数的方式传给对象。
verbose:
未有说明,试了一下,设置了改参数的值后将输出详细的说明,但是设置的值对这个输出结果的影响未知,据我观察输出的顺序多是随机的,也就是这里有值就行,下面是其中一次的输出结果:
一般情况下用不到,这里了解下就行。
注意:
我们在thread模块中说过,thread.start_new_thread()函数不接受关键字传参,而这里的threading.Thread类则建议全部用关键字传参。
知道了如何创建thread对象之后,我们再来看看其内部有什么方法:
1.getName(self)
获取线程的名字,就是初始函数中的name的值。
同样的,我们可以使用实例的name属性来获取。
注意这里是类里面的方法,所以self不是要给的参数。
2.setName(self,name)
为线程设定名字name,和初始函数中的设定一样,不过可以用来重命名线程名。
3.start(self)
开始线程的运行,每个线程对象只有调用最多一次。
它将调用被调用对象的run()方法,并控制各个对象独立运行。
也就是说被调用的对象必要要有run()方法,在使用Thread类来实例化对象的时候,因为Thread中已经有了run()方法了,所以可以不用理。
但是,在基础Thread创建子类的时候,一般我们要重写子类的run()方法。
当一个线程对象重复调用start()方法时,将触发RuntimeError异常。
4.run(self)
方法表示线程是活跃的,也就是要运行线程。
我们可以在子类中重写这个方法。
它和start()的差别在于:
start()更像是一个管理器,它负责调用对象的run()方法,而run()方法更像是一个入口函数。
如果单独运行run()方法,则只是运行了一个线程,而运行start()则会运行多个run()方法,并管理这些线程。
它们的关系更像:
这只是一个比喻理解,并不保证学术性上的正确。
所以,run更像是个人行为,而start更像是一个团队,而团队中有很多的个人。
一个人同时只能做一件事,而一个团队可以为每个人分配任务,那么这个团队同时就能做很多事情了。
我们再用代码来演示一下:
threads[i].run()#使用对象的run方法
可以看到又变成了单线程行为了。
5.isAlive(self)/is_alive
is_alive=isAlive(self),功能相同。
判断线程是否还活动着。
在线程调用了run()方法之后,而线程又没运行结束时,返回True,否则返回False。
(模块中的方法threading.enumerate()将返回所有活动线程对象,并将这些对象放在一个列表中,本质就是调用这个方法来判断线程是否活动。
)
6.join(self,timeout=None)
阻塞主线程,直到调用该方法的子线程运行完毕或者超时。
timeout表示超时时间,可以是一个数,例如整数,小数,分数,表示超时的时间,单位是秒。
返回值为None,可以在join超时之后调用isAlive确认线程是否结束。
如果线程还活动,说明join触发了超时,此时你可以继续调用join或者做其他处理。
当timeout没有给或者为None的时候,将阻塞直到调用此方法的子线程结束。
一个线程可以调用多次join方法。
为一个未开始的线程调用join将触发RuntimeError异常,当然函数内部主动触发的另算。
另外,如果试图使当前线程陷入死循环式的调用,也会触发该异常。
在开始的示例中我们看到了使用join的情况,现在我们来看看不使用join的情况:
threads=[]
t=threading.Thread(target=loop,args=(i,loops[i]))
threads.append(t)
threads[i].start()
看一下其输出:
可以看见主线程早就完成了,但是threading不同于thread模块,它还进行了收尾工作,也就是剩下的线程都运行完了。
阻塞主线程的意义更大是在于:
主线程需要子线程处理后的结果,所以需要等待子线程输出结果。
7.setDaemon(self,daemonic)
设置守护线程,若setDaemon(True),则代表将线程转入后台执行。
必须在线程开始之前调用设置,否则触发RuntimeError异常。
它的默认值继承于创建子线程的线程,在主线程中创建时,主线程属于非守护线程,所以其子线程的默认值也是False。
如果将某个线程设为后台进行,那么python的主线程在结束的时候不会进行收尾工作。
也就是说一旦主线程结束,整个进程也就结束了,那么子线程也就结束了。
其实说是守护线程,但其重要程度反而是最低的。
所以有些书也将其称为“不太重要的线程”。
代码示例:
loops=[10,5]
threads[0].setDaemon(True)#将第一个线程设为守护线程
print'
ctime()
main()
可以看到程序结束的时候,线程0并没有结束。
对守护线程使用join还是有效的。
8.isDaemon(self)
判断一个线程是否是守护线程,返回布尔值。
2.创建一个Thread的实例,传给它一个可调用的类
前面我们都是使用一个函数作为target,除此之外,我们还能以一个可调用的类为目标。
而所谓可调用的类,其实就是在类里面实现了__call__方法,下面看代码实例:
classThreadFunc(object):
def__init__(self,func,args):
self.func=func
self.args=args
def__call__(self):
#关键是要实现这个方法
self.func(self.args[0],self.args[1]) #apply(self.func,self.args)也有这种写法
t=threading.Thread(target=ThreadFunc(func=loop,args=(i,loops[i])),)#每次循环创建一个Thread的实例,目标是一个类
其核心就是通过调用类中的__call__方法,该方法又调用了传入的函数对象。
3.创建一个Thread的子类
这种方法更加通用,这种方法的关键是在子类内部重写run方法。
classMyThread(threading.Thread):
threading.Thread.__init__(self)#调用父类的构造函数
defrun(self):
apply(self.func,self.args)
t=MyThread(func=loop,args=(i,loops[i]))#使用我们自己的类来新建对象
一般推荐用这种方法,因为我们可以在类中实现其他方法,例如我并不在目标函数中输出结果,而是将结果作为返回值,而run方法得到这个返回值后,将其变成一个属性,这样只有我调用这个属性的时候,才会输出结果。
returnnloop+nsec#返回两个参数的和
self.result=apply(self.func,self.args)#调用函数的结果作为一个属性
执行结果为:
threads[i].result#打印该对象的属性
可以看出这种方法的扩展能力更强,所以一般推荐这种方法。
当然最终的选择还是看个人需求。
FUNCTIONS
1.Lock=allocate_lock(...)
和thread模块中的allocate_lock()方法一样,也是用来锁定线程的。
返回一个锁对象(LockType),该对象在thread模块中详细讲过了,这里不再多说。
2.RLock(*args,**kwargs)
这是一个工厂函数,返回一个可重入的锁对象。
和Lock的最大区别就是,该对象可以被重复的acquire(),也就是多次加锁。
所以解锁的时候也要解锁多次。
3.Condition(*args,**kwargs)
其实可以把它看做是一个高级的锁,比Lock,RLock更加高级,且功能更多,它能实现复杂的线程同步功能。
threadiong.Condition在内部维护一个琐对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。
Condition也提供了acquire,release方法,其含义与琐的acquire,release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。
除此之外Condition还提供了如下方法(特别要注意:
这些方法只有在占用琐(acquire)之后才能调用,否则将会报RuntimeError异常。
Condition.wait([timeout])
wait方法释放内部所占用的琐,同时线程被挂起,直至接收到通知被唤醒或超时(如果提供了timeout参数的话)。
当线程被唤醒并重新占有琐的时候,程序才会继续执行下去。
Condition.notify()
唤醒一个挂起的线程(如果存在挂起的线程)。
注意:
notify()方法不会释放所占用的琐。
Condition.notifyAll()/Condition.notify_all()
唤醒所有挂起的线程(如果存在挂起的线程)。
方法不会释放所占用的琐。
为了方便大家理解,我去网上找了一个例子:
戳这里
这个例子是一个捉迷藏的游戏,来具体介绍threading.Condition的基本使用。
假设这个游戏由两个人来玩,一个藏(Hider),一个找(Seeker)。
游戏的规则如下:
1.游戏开始之后,Seeker先把自己眼睛蒙上,蒙上眼睛后,就通知Hider;
2.Hider接收通知后开始找地方将自己藏起来,藏好之后,再通知Seeker可以找了;
3.Seeker接收到通知之后,就开始找Hider。
Hider和Seeker都是独立的个体,在程序中用两个独立的线程来表示,在游戏过程中,两者之间的行为有一定的时序关系,我们通过Condition来控制这种时序关系。
classSeeker(threading.Thread):
def__init__(self,cond,name,):
threading.Thread.__init__(self)
self.cond=cond
self.name=name
time.sleep
(1)#确保先运行Hider中的方法
self.cond.acquire()#2加锁
printself.name+'
我已经把眼睛蒙上了'
self.cond.notify()#4唤醒一个被挂起的线程,因为本线程并没有挂起,所以唤醒的是下面的
self.cond.wait()#5挂起,等待别的线程唤醒
我找到你了~_~'
self.cond.notify()#7唤醒别的休眠线程
self.cond.release()#8释放锁
我赢了'
#9结束
classHider(threading.Thread):
def__init__(self,cond,name):
self.cond.acquire()#1加锁
self.cond.wait()#3释放对琐的占用,同时线程挂起在这里,直到被notify并重新占有琐
我已经藏好了,你快来找我吧'
self.cond.notify()#6唤醒一个被休眠的线程
self.cond.wait()#7挂起当前线程
self.cond.release()#8被唤醒后释放锁
被你找到了,哎~~~'
cond=threading.Condition()
seeker=Seeker(cond,'
seeker'
hider=Hider(cond,'
hider'
seeker.start()
hider.start()
这个例子可能和原文中的不一样,因为原文中写反了,同时我也稍微修改了
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- python 多线程 threading 模块 剖析