• 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需要得到每个方法的处理结果,然后进行综合分析。 为了不让整个系统的执行时间被某个方法拖的很长,必须对每个处理蛋白质的方法进行超时控制, 在规定的时间里面如果该方法没有给出结果,自动将其忽略。

  • 相关阅读:
    【深入理解Java虚拟机】类加载机制
    【深入理解Java虚拟机】四种引用类型的特点
    【深入理解Java虚拟机】JVM内存区域
    【每日一题】单词搜索
    过滤器和拦截器有啥区别,这次会了!
    Filter过滤器简单入门
    SpringMVC的运行流程+常用注解总结
    SpringAOP+源码解析,切就完事了
    Spring的循环依赖,学就完事了【附源码】
    Markdown的流程图制作
  • 原文地址:https://www.cnblogs.com/xinxihua/p/12839889.html
Copyright © 2020-2023  润新知