探尋python多線程ctrl+c退出問題解決方案
場景:
經(jīng)常會(huì)遇到下述問題:很多io busy的應(yīng)用采取多線程的方式來解決,但這時(shí)候會(huì)發(fā)現(xiàn)python命令行不響應(yīng)ctrl-c 了,而對(duì)應(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則會(huì)結(jié)束程序
而對(duì)應(yīng)的python代碼:
# -*- coding: utf-8 -*-
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_.setDaemon(True)
thread_.start()
python p.py
后ctrl-c則完全不起作用了。
不成熟的分析:
首先單單設(shè)置 daemon 為 true 肯定不行,就不解釋了。當(dāng)daemon為 false 時(shí),導(dǎo)入python線程庫后實(shí)際上,threading會(huì)在主線程執(zhí)行完畢后,檢查是否有不是 daemon 的線程,有的化就wait,等待線程結(jié)束了,在主線程等待期間,所有發(fā)送到主線程的信號(hào)也會(huì)被阻測,可以在上述代碼加入signal模塊驗(yàn)證一下:
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é)束后才會(huì)出現(xiàn)打印 "main-thread exit",可見 ctrl-c被阻測了
threading 中在主線程結(jié)束時(shí)進(jìn)行的操作:
_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()
對(duì)所有的非daemon線程進(jìn)行join等待,其中join中可自行察看源碼,又調(diào)用了wait,同上文分析 ,主線程等待到了一把鎖上。
不成熟的解決:
只能把線程設(shè)成daemon才能讓主線程不等待,能夠接受ctrl-c信號(hào),但是又不能讓子線程立即結(jié)束,那么只能采用傳統(tǒng)的輪詢方法了,采用sleep間歇省點(diǎn)cpu吧:
# -*- coding: utf-8 -*-
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()
#主線程wait住了,不能接受信號(hào)了
#thread_.join()
def _exitCheckfunc():
print "ok"
try:
while 1:
alive=False
if thread_.isAlive():
alive=True
if not alive:
break
time.sleep(1)
#為了使得統(tǒng)計(jì)時(shí)間能夠運(yùn)行,要捕捉 KeyboardInterrupt :ctrl-c
except KeyboardInterrupt, e:
traceback.print_exc()
print "consume time :",time.time()-start
threading._shutdown=_exitCheckfunc
缺點(diǎn):輪詢總會(huì)浪費(fèi)點(diǎn)cpu資源,以及battery.
有更好的解決方案敬請(qǐng)?zhí)岢觥?/p>
ps1: 進(jìn)程監(jiān)控解決方案 :
用另外一個(gè)進(jìn)程來接受信號(hào)后殺掉執(zhí)行任務(wù)進(jìn)程,牛
# -*- coding: utf-8 -*-
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:
"""this class solves two problems with multithreaded
programs in Python, (1) a signal might be delivered
to any thread (which is just a malfeature) and (2) if
the thread that gets the signal is waiting, the signal
is ignored (which is a bug).
The watcher is a concurrent process (not thread) that
waits for a signal and the process that contains the
threads. See Appendix A of The Little Book of Semaphores.
http://greenteapress.com/semaphores/
I have only tested this on Linux. I would expect it to
work on the Macintosh and not work on Windows.
"""
def __init__(self):
""" Creates a child thread, which returns. The parent
thread waits for a KeyboardInterrupt and then kills
the child thread.
"""
self.child = os.fork()
if self.child == 0:
return
else:
self.watch()
def watch(self):
try:
os.wait()
except KeyboardInterrupt:
# I put the capital B in KeyBoardInterrupt so I can
# tell when the Watcher gets the SIGINT
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é)束
相關(guān)文章
python基于TCP實(shí)現(xiàn)的文件下載器功能案例
這篇文章主要介紹了python基于TCP實(shí)現(xiàn)的文件下載器功能,結(jié)合具體實(shí)例形式分析了Python使用socket模塊實(shí)現(xiàn)的tcp協(xié)議下載功能客戶端與服務(wù)器端相關(guān)操作技巧,需要的朋友可以參考下2019-12-12
python GUI庫圖形界面開發(fā)之PyQt5窗口布局控件QStackedWidget詳細(xì)使用方法
這篇文章主要介紹了python GUI庫圖形界面開發(fā)之PyQt5窗口布局控件QStackedWidget詳細(xì)使用方法,需要的朋友可以參考下2020-02-02
Python中使用moviepy進(jìn)行視頻分割的實(shí)現(xiàn)方法
MoviePy是一個(gè)關(guān)于視頻編輯的python庫,主要包括:剪輯,嵌入拼接,標(biāo)題插入,視頻合成(又名非線性編輯),視頻處理,和自定制效果。本文重點(diǎn)給大家介紹Python中使用moviepy進(jìn)行視頻分割的實(shí)現(xiàn)方法,需要的朋友一起看看吧2021-12-12
Python請(qǐng)求庫發(fā)送HTTP POST請(qǐng)求的示例代碼
這段代碼使用了Python的requests庫來發(fā)送HTTP POST請(qǐng)求,向本地服務(wù)器的API發(fā)送數(shù)據(jù),并處理響應(yīng),一步步解釋這個(gè)代碼2024-08-08
深入學(xué)習(xí)Python可變與不可變對(duì)象操作實(shí)例
Python中的數(shù)據(jù)類型可以分為可變對(duì)象和不可變對(duì)象,了解它們之間的區(qū)別對(duì)于編寫高效的Python代碼至關(guān)重要,本文將詳細(xì)介紹可變對(duì)象和不可變對(duì)象的概念,以及如何正確地使用它們來提高代碼的性能和可讀性2023-12-12
Python中json格式數(shù)據(jù)的編碼與解碼方法詳解
這篇文章主要介紹了Python中json格式數(shù)據(jù)的編碼與解碼方法,詳細(xì)分析了Python針對(duì)json格式數(shù)據(jù)的編碼轉(zhuǎn)換操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07
Python Pandas知識(shí)點(diǎn)之缺失值處理詳解
這篇文章主要給大家介紹了關(guān)于Pandas知識(shí)點(diǎn)之缺失值處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05

