Python 中用 Ctrl+C 終止多線程程序的問題解決

花了一天時間用python爲服務寫了個壓力測試。很簡單,多線程向服務器發請求。但寫完以後發現若是中途想停下來,按Ctrl+C達不到效果,天然想到要用信號處理函數捕捉信號,使線程都停下來,問題解決的方法請往下看: python

#!/bin/env python
# -*- coding: utf-8 -*-
#filename: peartest.py

import threading, signal

is_exit = False

def doStress(i, cc):
    global is_exit
    idx = i
    while not is_exit:
    if (idx < 10000000):
        print "thread[%d]: idx=%d"%(i, idx)
        idx = idx + cc
    else:
        break
    print "thread[%d] complete."%i

def handler(signum, frame):
    global is_exit
    is_exit = True
    print "receive a signal %d, is_exit = %d"%(signum, is_exit)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, handler)
    signal.signal(signal.SIGTERM, handler)
    cc = 5
    for i in range(cc):
        t = threading.Thread(target=doStress, args=(i,cc))
        t.start()

上面是一個模擬程序,並不真正向服務發送請求,而代之以在一千萬之內,每一個線程每隔併發數個(cc個)打印一個整數。很明顯,當全部線程都完成本身的任務後,進程會正常退出。但若是咱們中途想退出(試想一個壓力測試程序,在中途已經發現了問題,須要中止測試),該腫麼辦?你固然能夠用ps查找到進程號,而後kill -9殺掉,但這樣太繁瑣了,捕捉Ctrl+C是最天然的想法。上面示例程序中已經捕捉了這個信號,並修改全局變量is_exit,線程中會檢測這個變量,及時退出。 服務器

但事實上這個程序並不work,當你按下Ctrl+C時,程序照常運行,並沒有任何響應。網上搜了一些資料,明白是python的子線程若是不是daemon的話,主線程是不能響應任何中斷的。但設爲daemon後主線程會隨之退出,接着整個進程很快就退出了,因此還須要在主線程中檢測各個子線程的狀態,直到全部子線程退出後本身才退出,所以上例29行以後的代碼能夠修改成: 多線程

threads=[]
for i in range(cc):
    t = threading.Thread(target=doStress, args=(i, cc))
    t.setDaemon(True)
    threads.append(t)
    t.start()
for i in range(cc):
    threads[i].join()

從新試一下,問題依然沒有解決,進程仍是沒有響應Ctrl+C,這是由於join()函數一樣會waiting在一個鎖上,使主線程沒法捕獲信號。所以繼續修改,調用線程的isAlive()函數判斷線程是否完成: 併發

while 1:
    alive = False
    for i in range(cc):
        alive = alive or threads[i].isAlive()
    if not alive:
    break

這樣修改後,程序徹底按照預想運行了:能夠順利的打印每一個線程應該打印的全部數字,也能夠中途用Ctrl+C終結整個進程。完整的代碼以下: app

#!/bin/env python
# -*- coding: utf-8 -*-
#filename: peartest.py

import threading, signal

is_exit = False

def doStress(i, cc):
    global is_exit
    idx = i
    while not is_exit:
        if (idx < 10000000):
            print "thread[%d]: idx=%d"%(i, idx)
            idx = idx + cc
        else:
            break
    if is_exit:
        print "receive a signal to exit, thread[%d] stop."%i
    else:
        print "thread[%d] complete."%i

def handler(signum, frame):
    global is_exit
    is_exit = True
    print "receive a signal %d, is_exit = %d"%(signum, is_exit)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, handler)
    signal.signal(signal.SIGTERM, handler)
    cc = 5
    threads = []
    for i in range(cc):
    t = threading.Thread(target=doStress, args=(i,cc))
    t.setDaemon(True)
    threads.append(t)
    t.start()
    while 1:
        alive = False
        for i in range(cc):
            alive = alive or threads[i].isAlive()
        if not alive:
            break

其實,若是用python寫一個服務,也須要這樣,由於負責服務的那個線程是永遠在那裏接收請求的,不會退出,而若是你想用Ctrl+C殺死整個服務,跟上面的壓力測試程序是一個道理。 函數

總結一下,python多線程中要響應Ctrl+C的信號以殺死整個進程,須要: 測試

1.把全部子線程設爲Daemon;
2.使用isAlive()函數判斷全部子線程是否完成,而不是在主線程中用join()函數等待完成;
3.寫一個響應Ctrl+C信號的函數,修改全局變量,使得各子線程可以檢測到,並正常退出。 線程

相關文章
相關標籤/搜索