zl程序教程

您现在的位置是:首页 >  后端

当前栏目

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

Python多线程解决方案 问题 退出 探寻 ctrl
2023-06-13 09:15:29 时间

场景:

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