当前位置 博文首页 > 探寻python多线程ctrl+c退出问题解决方案

    探寻python多线程ctrl+c退出问题解决方案

    作者:admin 时间:2021-07-15 18:52

    场景:

    经常会遇到下述问题:很多io busy的应用采取多线程的方式来解决,但这时候会发现python命令行不响应ctrl-c 了,而对应的java代码则没有问题:

    复制代码 代码如下:

    public class Test { 
        public static void main(String[] args) throws Exception { 
     
            new Thread(new Runnable() { 
     
                public void run() { 
                    long start = System.currentTimeMillis(); 
                    while (true) { 
                        try { 
                            Thread.sleep(1000); 
                        } catch (Exception e) { 
                        } 
                        System.out.println(System.currentTimeMillis()); 
                        if (System.currentTimeMillis() - start > 1000 * 100) break; 
                    } 
                } 
            }).start(); 
     
        } 

    java Test

    ctrl-c则会结束程序

    而对应的python代码:

    复制代码 代码如下:

    # -*- coding: utf-8 -*- 
    import time 
    import threading 
    start=time.time() 
    def foreverLoop(): 
        start=time.time() 
        while 1: 
            time.sleep(1) 
            print time.time() 
            if time.time()-start>100: 
                break 
                  
    thread_=threading.Thread(target=foreverLoop) 
    #thread_.setDaemon(True) 
    thread_.start() 

    python p.py

    后ctrl-c则完全不起作用了。

    不成熟的分析:

    首先单单设置 daemon 为 true 肯定不行,就不解释了。当daemon为 false 时,导入python线程库后实际上,threading会在主线程执行完毕后,检查是否有不是 daemon 的线程,有的化就wait,等待线程结束了,在主线程等待期间,所有发送到主线程的信号也会被阻测,可以在上述代码加入signal模块验证一下:

    复制代码 代码如下:

    def sigint_handler(signum,frame):   
        print "main-thread exit" 
        sys.exit()   
    signal.signal(signal.SIGINT,sigint_handler) 

    在100秒内按下ctrl-c没有反应,只有当子线程结束后才会出现打印 "main-thread exit",可见 ctrl-c被阻测了

    threading 中在主线程结束时进行的操作:

    复制代码 代码如下:

    _shutdown = _MainThread()._exitfunc 
    def _exitfunc(self): 
            self._Thread__stop() 
            t = _pickSomeNonDaemonThread() 
            if t: 
                if __debug__: 
                    self._note("%s: waiting for other threads", self) 
            while t: 
                t.join() 
                t = _pickSomeNonDaemonThread() 
            if __debug__: 
                self._note("%s: exiting", self) 
            self._Thread__delete() 
     

     对所有的非daemon线程进行join等待,其中join中可自行察看源码,又调用了wait,同上文分析 ,主线程等待到了一把锁上。

    不成熟的解决:

    只能把线程设成daemon才能让主线程不等待,能够接受ctrl-c信号,但是又不能让子线程立即结束,那么只能采用传统的轮询方法了,采用sleep间歇省点cpu吧:
     

    复制代码 代码如下:

    # -*- coding: utf-8 -*- 
    import time,signal,traceback 
    import sys 
    import threading 
    start=time.time() 
    def foreverLoop(): 
        start=time.time() 
        while 1: 
            time.sleep(1) 
            print time.time() 
            if time.time()-start>5: 
                break 
                 
    thread_=threading.Thread(target=foreverLoop) 
    thread_.setDaemon(True) 
    thread_.start() 
     
    #主线程wait住了,不能接受信号了 
    #thread_.join() 
     
    def _exitCheckfunc(): 
        print "ok" 
        try: 
            while 1: 
                alive=False 
                if thread_.isAlive(): 
                    alive=True 
                if not alive: 
                    break 
                time.sleep(1)   
        #为了使得统计时间能够运行,要捕捉  KeyboardInterrupt :ctrl-c       
        except KeyboardInterrupt, e: 
            traceback.print_exc() 
        print "consume time :",time.time()-start 
             
    threading._shutdown=_exitCheckfunc 

       缺点:轮询总会浪费点cpu资源,以及battery.

    有更好的解决方案敬请提出。

    ps1: 进程监控解决方案 :

    用另外一个进程来接受信号后杀掉执行任务进程,牛

    复制代码 代码如下:

    # -*- coding: utf-8 -*- 
    import time,signal,traceback,os 
    import sys 
    import threading 
    start=time.time() 
    def foreverLoop(): 
        start=time.time() 
        while 1: 
            time.sleep(1) 
            print time.time() 
            if time.time()-start>5: 
                break 
     
    class Watcher: 
        """this class solves two problems with multithreaded
        programs in Python, (1) a signal might be delivered
        to any thread (which is just a malfeature) and (2) if
        the thread that gets the signal is waiting, the signal
        is ignored (which is a bug).
     
        The watcher is a concurrent process (not thread) that
        waits for a signal and the process that contains the
        threads.  See Appendix A of The Little Book of Semaphores.
        http://greenteapress.com/semaphores/
     
        I have only tested this on Linux.  I would expect it to
        work on the Macintosh and not work on Windows.
        """ 
     
        def __init__(self): 
            """ Creates a child thread, which returns.  The parent
                thread waits for a KeyboardInterrupt and then kills
                the child thread.
            """ 
            self.child = os.fork() 
            if self.child == 0: 
                return 
            else: 
                self.watch() 
     
        def watch(self): 
            try: 
                os.wait() 
            except KeyboardInterrupt: 
                # I put the capital B in KeyBoardInterrupt so I can 
                # tell when the Watcher gets the SIGINT 
                print 'KeyBoardInterrupt' 
                self.kill() 
            sys.exit() 
     
        def kill(self): 
            try: 
                os.kill(self.child, signal.SIGKILL) 
            except OSError: pass 
     
    Watcher()             
    thread_=threading.Thread(target=foreverLoop) 
    thread_.start() 

     注意 watch()一定要放在线程创建前,原因未知。。。。,否则立刻就结束

    jsjbwy