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

yipeiwu_com6年前Python基础

场景:

经常会遇到下述问题:很多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()一定要放在线程创建前,原因未知。。。。,否则立刻就结束

相关文章

Python2.7基于淘宝接口获取IP地址所在地理位置的方法【测试可用】

Python2.7基于淘宝接口获取IP地址所在地理位置的方法【测试可用】

本文实例讲述了Python2.7基于淘宝接口获取IP地址所在地理位置的方法。分享给大家供大家参考,具体如下: #!/usr/bin/python import sys, os, u...

Python实现的一个简单LRU cache

起因:我的同事需要一个固定大小的cache,如果记录在cache中,直接从cache中读取,否则从数据库中读取。python的dict 是一个非常简单的cache,但是由于数据量很大,内...

Python语法快速入门指南

Python语法快速入门指南

Python语言与Perl,C和Java等语言有许多相似之处。但是,也存在一些差异。 在本章中我们将来学习Python的基础语法,让你快速学会Python编程。 第一个Python程序...

django实现用户登陆功能详解

django实现用户登陆功能详解

简介: Python下有许多款不同的 Web 框架。Django是重量级选手中最有代表性的一位。许多成功的网站和APP都基于Django。 Django是一个开放源代码的Web应用框...

python添加模块搜索路径方法

1.函数添加 import sys sys.path sys.path.append("c:\\") 2.修改pythonpath(试不通) windows:PYTHONPATH 3.增...