探寻python多线程ctrl+c退出问题解决方案
场景:
经常会遇到下述问题:很多iobusy的应用采取多线程的方式来解决,但这时候会发现python命令行不响应ctrl-c了,而对应的java代码则没有问题:
publicclassTest{ publicstaticvoidmain(String[]args)throwsException{ newThread(newRunnable(){ publicvoidrun(){ longstart=System.currentTimeMillis(); while(true){ try{ Thread.sleep(1000); }catch(Exceptione){ } System.out.println(System.currentTimeMillis()); if(System.currentTimeMillis()-start>1000*100)break; } } }).start(); } } javaTest
ctrl-c则会结束程序
而对应的python代码:
#-*-coding:utf-8-*- importtime importthreading start=time.time() defforeverLoop(): start=time.time() while1: time.sleep(1) printtime.time() iftime.time()-start>100: break thread_=threading.Thread(target=foreverLoop) #thread_.setDaemon(True) thread_.start()
pythonp.py
后ctrl-c则完全不起作用了。
不成熟的分析:
首先单单设置daemon为true肯定不行,就不解释了。当daemon为false时,导入python线程库后实际上,threading会在主线程执行完毕后,检查是否有不是daemon的线程,有的化就wait,等待线程结束了,在主线程等待期间,所有发送到主线程的信号也会被阻测,可以在上述代码加入signal模块验证一下:
defsigint_handler(signum,frame): print"main-threadexit" sys.exit() signal.signal(signal.SIGINT,sigint_handler)
在100秒内按下ctrl-c没有反应,只有当子线程结束后才会出现打印"main-threadexit",可见ctrl-c被阻测了
threading中在主线程结束时进行的操作:
_shutdown=_MainThread()._exitfunc def_exitfunc(self): self._Thread__stop() t=_pickSomeNonDaemonThread() ift: if__debug__: self._note("%s:waitingforotherthreads",self) whilet: 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-*- importtime,signal,traceback importsys importthreading start=time.time() defforeverLoop(): start=time.time() while1: time.sleep(1) printtime.time() iftime.time()-start>5: break thread_=threading.Thread(target=foreverLoop) thread_.setDaemon(True) thread_.start() #主线程wait住了,不能接受信号了 #thread_.join() def_exitCheckfunc(): print"ok" try: while1: alive=False ifthread_.isAlive(): alive=True ifnotalive: break time.sleep(1) #为了使得统计时间能够运行,要捕捉 KeyboardInterrupt:ctrl-c exceptKeyboardInterrupt,e: traceback.print_exc() print"consumetime:",time.time()-start threading._shutdown=_exitCheckfunc
缺点:轮询总会浪费点cpu资源,以及battery.
有更好的解决方案敬请提出。
ps1:进程监控解决方案:
用另外一个进程来接受信号后杀掉执行任务进程,牛
#-*-coding:utf-8-*- importtime,signal,traceback,os importsys importthreading start=time.time() defforeverLoop(): start=time.time() while1: time.sleep(1) printtime.time() iftime.time()-start>5: break classWatcher: """thisclasssolvestwoproblemswithmultithreaded programsinPython,(1)asignalmightbedelivered toanythread(whichisjustamalfeature)and(2)if thethreadthatgetsthesignaliswaiting,thesignal isignored(whichisabug). Thewatcherisaconcurrentprocess(notthread)that waitsforasignalandtheprocessthatcontainsthe threads. SeeAppendixAofTheLittleBookofSemaphores. http://greenteapress.com/semaphores/ IhaveonlytestedthisonLinux. Iwouldexpectitto workontheMacintoshandnotworkonWindows. """ def__init__(self): """Createsachildthread,whichreturns. Theparent threadwaitsforaKeyboardInterruptandthenkills thechildthread. """ self.child=os.fork() ifself.child==0: return else: self.watch() defwatch(self): try: os.wait() exceptKeyboardInterrupt: #IputthecapitalBinKeyBoardInterruptsoIcan #tellwhentheWatchergetstheSIGINT print'KeyBoardInterrupt' self.kill() sys.exit() defkill(self): try: os.kill(self.child,signal.SIGKILL) exceptOSError:pass Watcher() thread_=threading.Thread(target=foreverLoop) thread_.start()
注意watch()一定要放在线程创建前,原因未知。。。。,否则立刻就结束