詳解python如何優(yōu)雅地關(guān)閉線程
在并發(fā)編程中,我們可能會(huì)創(chuàng)建新線程,并在其中運(yùn)行任務(wù),可能由于一些原因,決定停止該線程。例如:
- 不再需要線程任務(wù)的結(jié)果了。
- 應(yīng)用程序正在關(guān)閉。
- 線程執(zhí)行可能已經(jīng)出現(xiàn)了異常
Threading 模塊的 Thread 類(lèi)并沒(méi)有提供關(guān)閉線程的方法。如果不正確關(guān)閉子線程,可能遇到如下問(wèn)題:
- 中止主線程后,子線程仍然在運(yùn)行,成為僵尸進(jìn)程
- 子線程打開(kāi)的文件未能正確關(guān)閉,造成數(shù)據(jù)丟失
- 子線程打開(kāi)的數(shù)據(jù)庫(kù),未能提交更新,造成數(shù)據(jù)丟失
那么應(yīng)該如何正確關(guān)閉線程呢?
1. Python 默認(rèn)關(guān)閉線程的方式
線程對(duì)象創(chuàng)建后,調(diào)用start(方法運(yùn)行, 執(zhí)行結(jié)束后,自動(dòng)關(guān)閉。如下面的示例代碼:
#!/usr/bin/python # -*- coding: UTF-8 -*- import threading #導(dǎo)入threading 模塊 import time # 定義任務(wù)函數(shù) print_time def print_time ( threadName ,delay ): count = 0 while count < 5: time.sleep(delay) count += 1 print("%s: %s \n" % (threadName,time.ctime(time.time()))) # 定義任務(wù)函數(shù) print_cube def print_cube(num): #pring cube print("Cube:{} \n".format(num*num*num)) # 創(chuàng)建兩個(gè)線程 if __name__ == "__main__": # 創(chuàng)建兩個(gè)子線程 t1 = threading.Thread( target=print_cube,args=(10,)) t2 = threading.Thread( target=print_time,args=("Thread-2",4,)) #start threads t1.start() # start 后,子線程開(kāi)始運(yùn)行 t2.start() t1.join() #join 命令:讓主線程暫停運(yùn)行,等待子線程運(yùn)行結(jié)束。 t2.join() print("Done") # The statement is executed after sub threads done
2. 如何優(yōu)雅地關(guān)閉線程?
上節(jié)的例子,線程執(zhí)行時(shí)間短,很快可以結(jié)束,所以主線程可以等待其結(jié)束。但是如果子線程執(zhí)行的是1個(gè)耗時(shí)任務(wù),如提供1個(gè)服務(wù),或執(zhí)行1個(gè)Monitor 任務(wù),子線程內(nèi)可能存在永久循環(huán),這時(shí)子線程對(duì)象運(yùn)行start()后,就一直處理運(yùn)行狀態(tài)。
在WIndows系統(tǒng),如果應(yīng)用程序直接退出,子線程自然也被強(qiáng)行中止,但子線程正在執(zhí)行的任務(wù)可能會(huì)受影響,如正在存取的文件可能正確關(guān)閉,造成數(shù)據(jù)丟失等。
在Linux系統(tǒng),如果應(yīng)用程序直接退出,如使用kill命令殺死進(jìn)程,未正確關(guān)閉的子線程可能仍在運(yùn)行,成為僵尸進(jìn)程。
那么如何優(yōu)雅地停止子線程呢?思路有兩個(gè):
1) 通過(guò)設(shè)置全局狀態(tài)變量來(lái)關(guān)閉線程
2) 通過(guò) threading.Event 對(duì)象來(lái)關(guān)閉線程
下面示例展示兩種方法的實(shí)現(xiàn)過(guò)程
2.1. 使用全局變量來(lái)關(guān)閉線程
實(shí)現(xiàn)步驟:
- 在線程內(nèi)添加狀態(tài)變量
- 線程循環(huán)體內(nèi),檢測(cè)狀態(tài)變量,如果為False ,退出循環(huán)。
- 主線程需要關(guān)閉線程時(shí),將子線程對(duì)象的狀態(tài)變量置為False即可。
2.1.1 關(guān)閉 thread類(lèi)實(shí)現(xiàn)的線程
class CountdownTask: def __init__(self): self._running = True # 定義線程狀態(tài)變量 def terminate(self): self._running = False def run(self, n): # run方法的主循環(huán)條件加入對(duì)狀態(tài)變量的判斷 while self._running and n > 0: print('T-minus', n) n -= 1 time.sleep(5) print("thread is ended") c = CountdownTask() th = Thread(target = c.run, args =(10, )) th.start() # 對(duì)于耗時(shí)線程,沒(méi)必要再用join()方法了,注意主線程通常也需要有個(gè)監(jiān)控循環(huán) # … any code … # Signal termination q = input("please press any key to quit ") c.terminate()
2.1.2 關(guān)閉函數(shù)式線程
關(guān)閉函數(shù)式線程,可以用全局變量做狀態(tài)變量
import threading import time def run(): while True: print('thread running') global stop_threads if stop_threads: break stop_threads = False t1 = threading.Thread(target = run) t1.start() time.sleep(1) stop_threads = True t1.join() print('thread killed')
2.2. 使用 threading.Event 對(duì)象關(guān)閉子線程
2.2.1 Event 機(jī)制工作原理
Event 是線程間通信的一種方式。其作用相當(dāng)于1個(gè)全局flag,主線程通過(guò)控制 event 對(duì)象狀態(tài),來(lái)協(xié)調(diào)子線程步調(diào)。
使用方式
- 主線程創(chuàng)建 event 對(duì)象,并將其做為參數(shù)傳給子線程
- 主線程可以用
set()
方法將event
對(duì)象置為true, 用clear()
方法將其置為false。 - 子線程循環(huán)體內(nèi),檢查 event 對(duì)象的值,如果為 True, 則退出循環(huán)。
- 子線程,可使用
event.wait()
將阻塞當(dāng)前子進(jìn)程,直至event 對(duì)象被置為true.
event 類(lèi)的常用方法
- set() 設(shè)置 True
- clear() 設(shè)置 False,
- wait() 使進(jìn)程等待,直到flag被改為true.
- is_set() 查詢 event 對(duì)象,如被設(shè)置為真,則返回True, 否則返回False.
if event.is_set(): # do something before end worker break
這種方式的優(yōu)點(diǎn)是,Event對(duì)象是線程安全的,而且速度更快,推薦使用這種方式關(guān)閉耗時(shí)線程。
2.2.2 完整代碼:
from time import sleep from threading import Thread from threading import Event # define task function def task(event): # execute a task in a loop for i in range(100): # block for a moment sleep(1) # check for stop if event.is_set(): # 在此添加退出前要做的工作,如保存文件等 break # report a message print('Worker thread running...') print('Worker is ended') # create the event event = Event() # create a thread thread = Thread(target=task, args=(event,)) # start the new thread thread.start() # block for a while sleep(3) # stop the worker thread print('Main stopping thread') event.set() # 這里是為了演示,實(shí)際開(kāi)發(fā)時(shí),主進(jìn)程有事件循環(huán),耗時(shí)函數(shù)不需要調(diào)用join()方法 thread.join()
子線程執(zhí)行其任務(wù)循環(huán),它每次循環(huán)都會(huì)檢查event對(duì)象,該對(duì)象保持 false,就不會(huì)觸發(fā)線程停止。
當(dāng)主線程調(diào)用event對(duì)象的 set() 方法后,在子線程循環(huán)體內(nèi),調(diào)用event對(duì)象is_set()方法,發(fā)現(xiàn)event 對(duì)象為T(mén)rue后, 立即退出任務(wù)循環(huán),結(jié)束運(yùn)行。
到此這篇關(guān)于詳解python如何優(yōu)雅地關(guān)閉線程的文章就介紹到這了,更多相關(guān)python關(guān)閉線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python計(jì)算Content-MD5并獲取文件的Content-MD5值方式
這篇文章主要介紹了python計(jì)算Content-MD5并獲取文件的Content-MD5值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04Python性能調(diào)優(yōu)的十個(gè)小技巧總結(jié)
大家好,今天這篇文章關(guān)于Python性能調(diào)優(yōu)的10個(gè)小技巧,每天花5-10分鐘閱讀我的文章,對(duì)你技術(shù)提升一定會(huì)有幫助。喜歡記得收藏以防迷路2021-11-11numpy中hstack vstack stack concatenate函數(shù)示例詳解
這篇文章主要為大家介紹了numpy中hstack vstack stack concatenate函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Python-re中search()函數(shù)的用法詳解(查找ip)
這篇文章主要介紹了Python-re中search()函數(shù)的用法-----查找ip,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03