场景:

 

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

 

Java代码  

python窗口按任意键退出 python ctrl+c 不能退出_python窗口按任意键退出

1. public class Test {  
2. public static void main(String[] args) throws Exception {  
3.   
4. new Thread(new Runnable() {  
5.   
6. public void run() {  
7. long start = System.currentTimeMillis();  
8. while (true) {  
9. try {  
10. 1000);  
11. catch (Exception e) {  
12.                     }  
13.                     System.out.println(System.currentTimeMillis());  
14. if (System.currentTimeMillis() - start > 1000 * 100) break;  
15.                 }  
16.             }  
17.         }).start();  
18.   
19.     }  
20. }

java Test

ctrl-c则会结束程序

 

而对应的python代码:

 

Python代码  

python窗口按任意键退出 python ctrl+c 不能退出_python窗口按任意键退出

1. # -*- coding: utf-8 -*-  
2. import time  
3. import threading  
4. start=time.time()  
5. def foreverLoop():  
6.     start=time.time()  
7. while 1:  
8. 1)  
9. print time.time()  
10. if time.time()-start>100:  
11. break  
12.               
13.   
14. thread_=threading.Thread(target=foreverLoop)  
15. #thread_.setDaemon(True)  
16. thread_.start()

 

python p.py

 

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

 

 

不成熟的分析:

 

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

 

Python代码  

python窗口按任意键退出 python ctrl+c 不能退出_python窗口按任意键退出

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

 

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

 

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

Python代码  

python窗口按任意键退出 python ctrl+c 不能退出_python窗口按任意键退出

1. _shutdown = _MainThread()._exitfunc  
2. def _exitfunc(self):  
3. self._Thread__stop()  
4.         t = _pickSomeNonDaemonThread()  
5. if t:  
6. if __debug__:  
7. self._note("%s: waiting for other threads", self)  
8. while t:  
9.             t.join()  
10.             t = _pickSomeNonDaemonThread()  
11. if __debug__:  
12. self._note("%s: exiting", self)  
13. self._Thread__delete()

 

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

 

不成熟的解决:

 

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

 

Python代码  

python窗口按任意键退出 python ctrl+c 不能退出_python窗口按任意键退出

1. # -*- coding: utf-8 -*-  
2. import time,signal,traceback  
3. import sys  
4. import threading  
5. start=time.time()  
6. def foreverLoop():  
7.     start=time.time()  
8. while 1:  
9. 1)  
10. print time.time()  
11. if time.time()-start>5:  
12. break  
13.               
14. thread_=threading.Thread(target=foreverLoop)  
15. thread_.setDaemon(True)  
16. thread_.start()  
17.   
18. #主线程wait住了,不能接受信号了  
19. #thread_.join()  
20.   
21. def _exitCheckfunc():  
22. print "ok"  
23. try:  
24. while 1:  
25. False  
26. if thread_.isAlive():  
27. True  
28. if not alive:  
29. break  
30. 1)    
31. #为了使得统计时间能够运行,要捕捉  KeyboardInterrupt :ctrl-c        
32. except KeyboardInterrupt, e:  
33.         traceback.print_exc()  
34. print "consume time :",time.time()-start  
35.           
36. threading._shutdown=_exitCheckfunc

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

 

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

 

ps1: 进程监控解决方案 :

 

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

Python代码  

python窗口按任意键退出 python ctrl+c 不能退出_python窗口按任意键退出

1. # -*- coding: utf-8 -*-  
2. import time,signal,traceback,os  
3. import sys  
4. import threading  
5. start=time.time()  
6. def foreverLoop():  
7.     start=time.time()  
8. while 1:  
9. 1)  
10. print time.time()  
11. if time.time()-start>5:  
12. break  
13.   
14. class Watcher:  
15. """this class solves two problems with multithreaded 
16.     programs in Python, (1) a signal might be delivered 
17.     to any thread (which is just a malfeature) and (2) if 
18.     the thread that gets the signal is waiting, the signal 
19.     is ignored (which is a bug). 
20.  
21.     The watcher is a concurrent process (not thread) that 
22.     waits for a signal and the process that contains the 
23.     threads.  See Appendix A of The Little Book of Semaphores. 
24.     http://greenteapress.com/semaphores/ 
25.  
26.     I have only tested this on Linux.  I would expect it to 
27.     work on the Macintosh and not work on Windows. 
28.     """  
29.   
30. def __init__(self):  
31. """ Creates a child thread, which returns.  The parent 
32.             thread waits for a KeyboardInterrupt and then kills 
33.             the child thread. 
34.         """  
35. self.child = os.fork()  
36. if self.child == 0:  
37. return  
38. else:  
39. self.watch()  
40.   
41. def watch(self):  
42. try:  
43.             os.wait()  
44. except KeyboardInterrupt:  
45. # I put the capital B in KeyBoardInterrupt so I can  
46. # tell when the Watcher gets the SIGINT  
47. print 'KeyBoardInterrupt'  
48. self.kill()  
49.         sys.exit()  
50.   
51. def kill(self):  
52. try:  
53. self.child, signal.SIGKILL)  
54. except OSError: pass  
55.   
56. Watcher()              
57. thread_=threading.Thread(target=foreverLoop)  
58. thread_.start()

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