線程的概念
現在的操作系統幾乎都支持運行多個任務,而在操作系統內部,一個任務往往代表的執行的某一個程序,也就是運行中的程序,運行的程序是一個動態的概念,也就是所說的進程,而在進程內部,往往有許多順序執行流,這些順序執行流就是線程。
線程的創建
Python提供了 _thread 和 threading 兩個模塊來支持多線程,其中 _thread 提供低級別的、原始的線程支持,以及一個簡單的鎖,正如它的名字所暗示的,一般編程不建議使用 thread 模塊;而 threading 模塊則提供了功能豐富的多線程支持。
Python 主要通過兩種方式來創建線程:
- 使用 threading 模塊的 Thread 類的構造器創建線程。
- 繼承 threading 模塊的 Thread 類創建線程類。
使用Thread類創建線程
Thread類的構造或者說初始化的__init__方法如下:
__init__(self, group=None, target=None, name=None, args=(), kwargs=None, *,daemon=None)
上面的構造器涉及如下幾個參數:
- group:指定該線程所屬的線程組。目前該參數還未實現,因此它只能設為 None。
- target:指定該線程要調度的目標方法。
- args:指定一個元組,以位置參數的形式為 target 指定的函數傳入參數。元組的第一個元素傳給 target 函數的第一個參數,元組的第二個元素傳給 target 函數的第二個參數……依此類推。
- kwargs:指定一個字典,以關鍵字參數的形式為 target 指定的函數傳入參數。
- daemon:指定所構建的線程是否為后代線程。
通過 Thread 類的構造器創建井啟動多線程的步驟如下:
- 調用 Thread 類的構造器創建線程對象。在創建線程對象時,target 參數指定的函數將作為線程執行體。
- 調用線程對象的 start() 方法啟動該線程。
示例:
import threading #定義一個函數,該函數返回當前執行的進程名字 def fc(value): for i in range(value):
# 調用threading模塊current_thread()函數獲取當前線程
# 線程對象的getName()方法獲取當前線程的名字
print(threading.current_thread().getName() + "--->" + str(i)) #定義線程執行體 for i in range(10): print(threading.current_thread().getName() + '--->' + str(i)) if i == 5: #當if條件成立時,執行子線程 sd = threading.Thread(target=fc, args=(10, )) sd.start() print("主線程執行完畢")
程序可以通過 setName(name) 方法為線程設置名字,也可以通過 geName() 方法返回指定線程的名字,這兩個方法可通過 name 屬性來代替。在默認情況下,主線程的名字為 MainThread,用戶啟動的多個線程的名字依次為 Thread-1、Thread-2、Thread-3、...、Thread-n 等。
繼承 threading 模塊的 Thread 類創建線程類
通過繼承 Thread 類來創建並啟動線程的步驟如下:
- 定義 Thread 類的子類,並重寫該類的 run() 方法。run() 方法的方法體就代表了線程需要完成的任務,因此把 run() 方法稱為線程執行體。
- 創建 Thread 子類的實例,即創建線程對象。
- 調用線程對象的 start() 方法來啟動線程。
示例:
import threading class DefineThread(threading.Thread): def __init__(self): #調用父類的初始化方法。 threading.Thread.__init__(self) #或者 #super().__init__() self.i = 0 #重新定義run方法,也就是線程的執行體 def run(self): while self.i < 5: print(threading.current_thread().name + "--->" + str(self.i)) self.i += 1 #主程序 for i in range(10): print(threading.current_thread().name + '--->' + str(i)) if i == 5: dt = DefineThread() dt.start() print("主線程執行完畢")
方法
join方法
Thread 提供了讓一個線程等待另一個線程完成的 join() 方法。當調用程序調用另外一個線程的join()方法時,需要等待另外一個線程執行完成才繼續執行當前的程序。也就是說當前的執行程序會被阻塞。
示例:
import threading def ft(value): for i in range(value): print(threading.current_thread().name + "--->" +str(i)) #主程序 for i in range(5): if i == 2: dt = threading.Thread(target=ft, args=(5, ), name = "被其他線程調用join()方法的線程") dt.start() dt.join() print(threading.current_thread().name + "--->" + str(i)) print("主線程執行完畢")
輸出如下:
MainThread--->0 MainThread--->1 被其他線程調用join()方法的線程--->0 被其他線程調用join()方法的線程--->1 被其他線程調用join()方法的線程--->2 被其他線程調用join()方法的線程--->3 被其他線程調用join()方法的線程--->4 MainThread--->2 MainThread--->3 MainThread--->4 主線程執行完畢
可以看出,當子線程在執行的時候,主線程是被阻塞的。
daemon(守護或者后台進程)
daemon屬性用來設置線程運行在后台,默認是運行在前台。當所有的前台進程都死掉時,后台進程也結束,不管后台進程是否運行完畢。
示例
import threading def ft(value): for i in range(value): print(threading.current_thread().name + "--->" +str(i)) t = threading.Thread(target=ft, args=(100, ), name = "后台線程", daemon=True) t.start() #如果沒有在創建線程對象指定daemon=True,那么也可以通過下面的屬性設置 #t = threading.Thread(target=ft, args=(100, ), name = "后台線程") #t.daemon = True #主程序 for i in range(5): print(threading.current_thread().name + "--->" + str(i)) print("主線程執行完畢")
上述程序在執行的時候,正常應該是t這個線程對象執行到99的時候才推出,但是,在主程序內部的線程執行完畢(也就是前台進程完畢),那么后台進程也會隨之退出。
從上面的程序可以看出,主線程默認是前台線程,t線程默認也是前台線程。但並不是所有的線程默認都是前台線程,有些線程默認就是后台線程。前台線程創建的子線程默認是前台線程,后台線程創建的子線程默認是后台線程。
可見,創建后台線程有兩種方式:
- 主動將線程的 daemon 屬性設置為 True。
- 后台線程啟動的線程默認是后台線程。
注意,當前台線程死亡后,Python 解釋器會通知后台線程死亡,但是從它接收指令到做出響應需要一定的時間。如果要將某個線程設置為后台線程,則必須在該線程啟動之前進行設置。也就是說,將 daemon 屬性設為 True,必須在 start() 方法調用之前進行,否則會引發 RuntimeError 異常。