有关Python中线程的超时控制以及一个简单的应用

一、 简单介绍

线程的超时控制在实际的应用中肯定是广泛存在的,比如网络连接超时(socket),文件处理超时等等,但是现在的编程语言貌似都没有很好的处理机制来实现超时管理(也可能是我孤陋寡闻,知道的弟兄不妨赐教下,感激不尽!),一般的说法都是不要特意的去从外部杀死一个线程,退出线程的正确方法是让线程中的run()方法运行结束或者如果run()方法是一个循环在run()方法里面设置一个选项变量来控制循环终止条件(其实还是让run()“自然死亡”)。有些编程语言,比如Python,在其多线程机制里面,如 threading.Thread,根本没有提供终止线程的方法(http://docs.python.org/library/threading.html#thread-objects )。

 

那么我们怎么让线程超时退出呢,或者说怎么实现超时管理? 其实这需要一点策略。

在说这方面的事情之前,首先了解下怎么在python里面编写多线程的程序,让你的类继承 threading.Thread,并且在类的__init__()方法里面首先调用threading.Thread的__init__()方法,而且你的类必须有一个无参数的run()方法,比如下面的例子:

 

#!/usr/bin/python # -*- coding: utf-8 -*- import threading ######################################################################## class MyThread(threading.Thread): """A simple threading class.""" #---------------------------------------------------------------------- def __init__(self): """Constructor""" threading.Thread.__init__(self, name = "Thread-1") #---------------------------------------------------------------------- def run(self): """The working method, put all the work of the class in it.""" print "I am a threading class, my name is: %s " % self.getName() print "I am stopping ..." mythread = MyThread() mythread.start()

 


二、Python中提供的线程超时检测机制

 

线程的超时与否可以用Python自己提供的机制来检测, 这就是线程的 join() 函数,在python的文档里面可以找到该函数的详细说明(http://docs.python.org/library/threading.html#threading.Thread.join )。 简单地说,如果同时执行了2个线程t1 和 t2,如果想让一个线程等待另一个线程执行结束再执行的话,那么必须执行被等待线程的join()方法,代码示例如下:

#---------------------------------------------------------------------- def test(): """A task control function.""" ... # previous job t1 = Thread1() t2 = Thread2() t1.start() t2.start() t2.join(10) # wait here until t2 is over or timeout occured(10 seconds) ... # the next job

 

通过上面的链接查到join()方法的文档可以知道, 该方法有一个可选的参数 timeout,  如果像上面的例子中设置了该参数的话, 执行了该函数会在此等待t2线程10秒钟,在此期间调用程序(caller)什么也不做,就等着,直到t2结束了或者超时了,才会执行下面的代码。如果不设置timeout参数, caller会在此等待直到t2运行结束。这里需要注意的是, join()函数在这里只相当于一个“采样”,它不会在超时的时候终止t2的执行,实际上t2在超时的情况下还是会执行直到其结束或者另一种情况,caller结束了,但是前提是t2必须被设置为“守护线程(daemon)”(详情见下面的应用实例)。

 

我们只是知道在那里等待了特定的时间再执行下面的代码,那么我们怎么判断t2是否是执行结束了还是线程超时了呢? 这就需要知道线程的"活动(alive)"的概念。大体上,一个线程自从start()方法被调用开始直到run()函数返回的这段期间都被认为是活动的, 而且python提供了一个方法 isAlive()来判断线程是否是活动的。对,就是这样,如果超时了的话,isAlive()方法肯定返回的True(因为join()方法不会结束线程,所以线程仍然是活动的), 而如果是执行结束了,run()函数肯定已经返回了,那么isAlive()方法肯定返回False。代码示例如下:


#---------------------------------------------------------------------- def test(): """A task control function.""" ... # previous job t1 = Thread1() t2 = Thread2() t1.start() t2.start() t2.join(10) # wait here until t2 is over or timeout occured(10 seconds) if t2.isAlive(): # if t2 is still alive, then it is time out! print 't2 is time out!' ... # the next job  

 

 

三、应用实例

 

超时以及python中的基本超时处理在上面已经为读者给出了,在这部分我将介绍一个超时控制和管理的简单实例。在这个例子中,我将有10个特种部队队员去执行刺杀恐怖分子的任务,每个人都有自己的刺杀目标,必须把自己的目标干掉才算完事! 每个人执行任务的时间肯定是不一样的,但是撤退的直升机只等待特定的时间就起飞,这意味着肯定有人因为超时而无法撤退,超时的人执行任务失败!

 

类1: Soldier, 执行任务的类。 有两个参数在这个类里面起到了至关重要的作用即 isStopped和isSuccess,前者说明这个类是否执行结束,后者说明这个类是否执行成功。 另外, isStopped还用于判断这个类是否是超时的, 我在这里不用前面说的isAlive()函数来判断,是因为isAlive()函数时间上太严格了,我需要让类自己设置一个是否停止的标志(我曾经看到过类已经运行完了,但是isAlive()函数还是返回True的情况)。每一个Soldier类都调用setDaemon(True)方法被设置为守护线程(daemon),所谓守护线程,就是在caller执行完毕的情况下,该线程也会结束,否则该线程会继续执行直到其真正的执行结束。Soldier类如下:

#!/usr/bin/python # -*- coding: utf-8 -*- import os, sys, re, math import threading import time import random ######################################################################## class Soldier(threading.Thread): """The class of a soldier.""" #---------------------------------------------------------------------- def __init__(self, name): """Constructor""" threading.Thread.__init__(self, name = name) self.name = name # the name of this soldier self.setDaemon(True) # this is a daemon thread. # the time of this soldier need to finish the job self.playTime = random.randint(1,10) # is the soldier stop shotting, if timeout or the target has been killed, # he may stop. self.isStopped = False self.isSuccess = False # did he kill the target? #---------------------------------------------------------------------- def assassinate(self): """The task, to kill the target.""" for i in range(self.playTime): print '%s play(%d)' % (self.name, i+1) time.sleep(1) #---------------------------------------------------------------------- def run(self): """Start to move ...""" print '%s has moved out, need time: %d ...' % (self.name, self.playTime) self.assassinate() print '%s stopped ...' % self.name self.isStopped = True # the target has been killed, then he stopped. self.isSuccess = True

 

类2: Commander, 指挥Soldier的类。这个类不是为了增加戏剧效果而特意加上的,而是这个类是必须的。执行任务的类需要有一个中间调用者来为其调用join()方法判断其是否超时。没错caller也可以在其开始执行的时候依次调用工作者类的join()方法来判断超时,但是这样的话,每次调用join()都需要等待,相当于线程不是一起执行而是一个接一个地执行。因此,为每个工作者类设计一个中间的调用类是必须的,caller依次启动这些中间的调用类,使这些类一起运行,那么所有的工作者类就是一起工作的,这才是多线程。 Commander类如下:

######################################################################## class Commander(threading.Thread): """The class of commander, a commander will command only one soldier.""" #---------------------------------------------------------------------- def __init__(self, soldier): """Constructor""" threading.Thread.__init__(self, name = 'Commander') self.soldier = soldier #---------------------------------------------------------------------- def run(self): """Authorize the soldier to start killing.""" self.soldier.start() try: # Boss said: give the soldier 5 seconds to finish his job self.soldier.join(5) except: pass # Use the class's own attribute to judge whether it is timeout. #if self.soldier.isAlive(): if not self.soldier.isStopped: print '%s is timeout!' % self.soldier.name # the soldier run out his time, then he stopped. self.soldier.isStopped = True

 

Caller: 执行所有的工作,包括初始化工作者类和中间调用类,到了规定时间检查执行结果等, caller的代码如下:

#---------------------------------------------------------------------- def killing(): """Let's pull the trigger, start killing !""" t1 = time.time() # Get ready for the commanders l_commander = [] for i in range(10): # 10 soldiers # get ready for the soldier soldier = Soldier('soldier-%d' % (i+1)) if i == 5 or i == 9: soldier.playTime = 10000 l_commander.append(Commander(soldier)) # Soldiers move out one by one. for cmd in l_commander: cmd.start() # Judge whether the helicopter should go. If all the soldiers are stop, # that is, all finished job or timeout, then it should go! isBreak = False while not isBreak: isBreak = True for cmd in l_commander: if cmd.soldier.isStopped == False: isBreak = False # Check the results of the battle at the schedule time. for cmd in l_commander: print '%s, is success: %s' % (cmd.soldier.name, cmd.soldier.isSuccess) # Go back to base. time.sleep(20) # Check the results at the final time. for cmd in l_commander: print '%s, is success: %s' % (cmd.soldier.name, cmd.soldier.isSuccess) t2 = time.time() print 'Total time: %.2f' % (float(t2-t1))

在caller中,用一个while循环来控制等待所有的线程stop而不执行下面的代码,一旦所有的线程stop了,则检查执行结果。

 

执行 killing()函数的结果如下:

soldier-1 has moved out, need time: 2 ... soldier-1 play(1) soldier-2 has moved out, need time: 6 ... soldier-2 play(1) soldier-3 has moved out, need time: 3 ... soldier-3 play(1) soldier-4 has moved out, need time: 4 ... soldier-4 play(1) soldier-5 has moved out, need time: 9 ... soldier-5 play(1) soldier-6 has moved out, need time: 10000 ... soldier-6 play(1) soldier-7 has moved out, need time: 8 ... soldier-7 play(1) soldier-8 has moved out, need time: 10 ... soldier-8 play(1) soldier-9 has moved out, need time: 7 ... soldier-9 play(1) soldier-10 has moved out, need time: 10000 ... soldier-10 play(1) soldier-3 play(2) soldier-2 play(2) soldier-4 play(2) soldier-1 play(2) soldier-6 play(2) soldier-7 play(2) soldier-5 play(2) soldier-8 play(2) soldier-9 play(2) soldier-10 play(2) soldier-1 stopped ... soldier-3 play(3) soldier-2 play(3) soldier-7 play(3) soldier-6 play(3) soldier-5 play(3) soldier-4 play(3) soldier-8 play(3) soldier-9 play(3) soldier-10 play(3) soldier-7 play(4) soldier-6 play(4) soldier-3 stopped ... soldier-4 play(4) soldier-2 play(4) soldier-8 play(4) soldier-5 play(4) soldier-9 play(4) soldier-10 play(4) soldier-7 play(5) soldier-6 play(5) soldier-4 stopped ... soldier-2 play(5) soldier-8 play(5) soldier-5 play(5) soldier-9 play(5) soldier-10 play(5) soldier-6 is timeout! soldier-2 is timeout! soldier-7 is timeout! soldier-8 is timeout! soldier-5 is timeout! soldier-7 play(6) soldier-6 play(6) soldier-2 play(6) soldier-8 play(6) soldier-5 play(6) soldier-9 is timeout! soldier-9 play(6) soldier-10 is timeout! soldier-1, is success: True soldier-2, is success: False soldier-3, is success: True soldier-4, is success: True soldier-5, is success: False soldier-6, is success: False soldier-7, is success: False soldier-8, is success: False soldier-9, is success: False soldier-10, is success: False soldier-10 play(6) soldier-7 play(7) soldier-6 play(7) soldier-2 stopped ... soldier-8 play(7) soldier-5 play(7) soldier-9 play(7) soldier-10 play(7) soldier-7 play(8) soldier-6 play(8) soldier-8 play(8) soldier-5 play(8) soldier-9 stopped ... soldier-10 play(8) soldier-7 stopped ... soldier-6 play(9) soldier-8 play(9) soldier-5 play(9) soldier-10 play(9) soldier-6 play(10) soldier-5 stopped ... soldier-8 play(10) soldier-10 play(10) soldier-6 play(11) soldier-8 stopped ... soldier-10 play(11) soldier-6 play(12) soldier-10 play(12) soldier-6 play(13) soldier-10 play(13) soldier-6 play(14) soldier-10 play(14) soldier-6 play(15) soldier-10 play(15) soldier-6 play(16) soldier-10 play(16) soldier-6 play(17) soldier-10 play(17) soldier-6 play(18) soldier-10 play(18) soldier-6 play(19) soldier-10 play(19) soldier-6 play(20) soldier-10 play(20) soldier-6 play(21) soldier-10 play(21) soldier-6 play(22) soldier-10 play(22) soldier-6 play(23) soldier-10 play(23) soldier-6 play(24) soldier-10 play(24) soldier-6 play(25) soldier-10 play(25) soldier-6 play(26) soldier-1, is success: True soldier-2, is success: True soldier-3, is success: True soldier-4, is success: True soldier-5, is success: True soldier-6, is success: False soldier-7, is success: True soldier-8, is success: True soldier-9, is success: True soldier-10, is success: False Total time: 25.05

 

结果中显示在5秒钟的时候检查战果, 只有1,3和4圆满的完成任务, 其他都超时。然后,在回到基地的20秒中里面, 其他的未完成的线程并没有停止,而是继续在工作, 20秒之后, caller也结束了,注意所有的工作者线程都已经设置成了守护线程,所以在caller结束的时候,也都跟着结束了。 在caller中,为了说明执行任务的类设置为守护线程与非守护线程的区别, 我特地让6号和10号队员的时间增加, 所以在最后将要结束的时候检查战果, 6和10还是没有完成, 依旧超时。

 

如果将Soldier类中的 self.setDaemon(True) 注释掉,那么6和10将会在caller结束的时候继续执行,直到10000秒后其真的运行结束。

 

四、总结

 

线程的超时控制和处理是非常实用的技术, 平时会用到很多。利用python自身提供的检测方法和自定义的控制项,我们可以很好地实现超时控制和管理。 在本文第三部分的例子中, 我展示了一种超时控制的策略, 即 工作者类+调用类+caller的模型, 里面的每个属性都是此模型里面的基础的和必须的。这个模型我目前正应用在了本人的一个项目上面, 在这个项目里面, 需要使用许多的计算机方法来处理一个蛋白质,每个方法都相当于本文例子里面的Soldier类,一个caller需要得到每个方法的处理结果,然后进行综合分析。 为了不让整个系统的执行时间被某个方法拖的很长,必须对每个处理蛋白质的方法进行超时控制, 在规定的时间里面如果该方法没有给出结果,自动将其忽略。

 

谢谢阅读, 欢迎继续讨论相关问题, < [email protected] > !

2010-11-27 于 杭州

你可能感兴趣的:(Python)