場景:
經(jīng)常會遇到下述問題:很多io busy的應(yīng)用采取多線程的方式來解決,但這時候會發(fā)現(xiàn)python命令行不響應(yīng)ctrl-c 了,而對應(yīng)的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則會結(jié)束程序
而對應(yīng)的python代碼:
- 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_.start()
python p.py
后ctrl-c則完全不起作用了。
不成熟的分析:
首先單單設(shè)置 daemon 為 true 肯定不行,就不解釋了。當(dāng)daemon為 false 時,導(dǎo)入python線程庫后實際上,threading會在主線程執(zhí)行完畢后,檢查是否有不是 daemon 的線程,有的化就wait,等待線程結(jié)束了,在主線程等待期間,所有發(fā)送到主線程的信號也會被阻測,可以在上述代碼加入signal模塊驗證一下:
- def sigint_handler(signum,frame):
- print "main-thread exit"
- sys.exit()
- signal.signal(signal.SIGINT,sigint_handler)
在100秒內(nèi)按下ctrl-c沒有反應(yīng),只有當(dāng)子線程結(jié)束后才會出現(xiàn)打印 "main-thread exit",可見 ctrl-c被阻測了
threading 中在主線程結(jié)束時進行的操作:
- _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中可自行察看源碼,又調(diào)用了wait,同上文分析 ,主線程等待到了一把鎖上。
不成熟的解決:
只能把線程設(shè)成daemon才能讓主線程不等待,能夠接受ctrl-c信號,但是又不能讓子線程立即結(jié)束,那么只能采用傳統(tǒng)的輪詢方法了,采用sleep間歇省點cpu吧:
- 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()
-
-
- def _exitCheckfunc():
- print "ok"
- try:
- while 1:
- alive=False
- if thread_.isAlive():
- alive=True
- if not alive:
- break
- time.sleep(1)
-
- except KeyboardInterrupt, e:
- traceback.print_exc()
- print "consume time :",time.time()-start
-
- threading._shutdown=_exitCheckfunc
缺點:輪詢總會浪費點cpu資源,以及battery.
有更好的解決方案敬請?zhí)岢觥?/strong>
ps1: 進程監(jiān)控解決方案 :
用另外一個進程來接受信號后殺掉執(zhí)行任務(wù)進程,牛
- 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:
-
-
- def __init__(self):
-
- self.child = os.fork()
- if self.child == 0:
- return
- else:
- self.watch()
-
- def watch(self):
- try:
- os.wait()
- except KeyboardInterrupt:
-
-
- 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()一定要放在線程創(chuàng)建前,原因未知。。。。,否則立刻就結(jié)束