探寻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模块验证一下:
在100秒内按下ctrl-c没有反应,只有当子线程结束后才会出现打印"main-threadexit",可见ctrl-c被阻测了 threading中在主线程结束时进行的操作: 对所有的非daemon线程进行join等待,其中join中可自行察看源码,又调用了wait,同上文分析,主线程等待到了一把锁上。 不成熟的解决: 只能把线程设成daemon才能让主线程不等待,能够接受ctrl-c信号,但是又不能让子线程立即结束,那么只能采用传统的轮询方法了,采用sleep间歇省点cpu吧: 缺点:轮询总会浪费点cpu资源,以及battery. 有更好的解决方案敬请提出。 ps1:进程监控解决方案: 用另外一个进程来接受信号后杀掉执行任务进程,牛 注意watch()一定要放在线程创建前,原因未知。。。。,否则立刻就结束
defsigint_handler(signum,frame):
print"main-threadexit"
sys.exit()
signal.signal(signal.SIGINT,sigint_handler)
_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()
#-*-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
#-*-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()
相关文章