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

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

 

解决方案


一个线程不能优雅地杀死另一个线程,因此对于您当前的代码,它foo永远不会终止。(使用thread.daemon = TruePython程序时,仅剩下守护程序线程将退出,但这不允许您在foo不终止主线程的情况下终止。)

有些人试图使用信号来停止执行,但是在某些情况下这可能是不安全的

如果可以修改foo,则有许多解决方案。例如,您可以检查是否threading.Event要退出while循环。

但是,如果您不能修改foo,则可以使用该multiprocessing模块在子进程中运行它,因为与线程不同,可以终止子进程。这是看起来的示例:

import time
import multiprocessing as mp

def foo(x = 1):
    cnt = 1
    while True:
        time.sleep(1)
        print(x, cnt)
        cnt += 1

def timeout(func, args = (), kwds = {}, timeout = 1, default = None):
    pool = mp.Pool(processes = 1)
    result = pool.apply_async(func, args = args, kwds = kwds)
    try:
        val = result.get(timeout = timeout)
    except mp.TimeoutError:
        pool.terminate()
        return default
    else:
        pool.close()
        pool.join()
        return val


if __name__ == '__main__':
    print(timeout(foo, kwds = {'x': 'Hi'}, timeout = 3, default = 'Bye'))
    print(timeout(foo, args = (2,), timeout = 2, default = 'Sayonara'))

产量

('Hi', 1)
('Hi', 2)
('Hi', 3)
Bye
(2, 1)
(2, 2)
Sayonara

注意,这也有一些限制。

  • 子流程接收父流程变量副本如果您在子流程中修改变量,则不会影响父流程。如果您的函数func需要修改变量,则需要使用共享变量

  • 参数(通过传递args)和关键字(kwds)必须是可腌制的。

  • 进程比线程更多的资源。通常,您只想在程序开始时创建一次多处理池。每次调用时都会timeout创建函数Pool这是必要的,因为我们需要pool.terminate()终止foo

 

 

一、 简单介绍

线程的超时控制在实际的应用中肯定是广泛存在的,比如网络连接超时(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)”(详情见下面的应用实例)。

--------------------------------------分割线 --------------------------------------

CentOS 6.4安装 Python2.7.10  http://www.linuxidc.com/Linux/2015-08/120895.htm

无需操作系统直接运行 Python 代码  http://www.linuxidc.com/Linux/2015-05/117357.htm

CentOS上源码安装Python3.4  http://www.linuxidc.com/Linux/2015-01/111870.htm

《Python核心编程 第二版》.(Wesley J. Chun ).[高清PDF中文版] http://www.linuxidc.com/Linux/2013-06/85425.htm

《Python开发技术详解》.( 周伟,宗杰).[高清PDF扫描版+随书视频+代码] http://www.linuxidc.com/Linux/2013-11/92693.htm

Python脚本获取Linux系统信息 http://www.linuxidc.com/Linux/2013-08/88531.htm

Ubuntu下用Python搭建桌面算法交易研究环境 http://www.linuxidc.com/Linux/2013-11/92534.htm

Python 语言的发展简史 http://www.linuxidc.com/Linux/2014-09/107206.htm

--------------------------------------分割线 --------------------------------------

我们只是知道在那里等待了特定的时间再执行下面的代码,那么我们怎么判断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需要得到每个方法的处理结果,然后进行综合分析。 为了不让整个系统的执行时间被某个方法拖的很长,必须对每个处理蛋白质的方法进行超时控制, 在规定的时间里面如果该方法没有给出结果,自动将其忽略。

posted on 2020-05-06 23:20  shuzihua  阅读(3601)  评论(0编辑  收藏  举报

导航