join和 Daemon守護線程
一、前言
一個程序至少有一個主線程,主線程啟動子線程後,它們之間並沒有隸屬關系。主線程和子線程執行是並行的,相互獨立。主線程執行完畢後默認不等子線程執行結束就接著往下走了,如果有其他程序就會運行另外的程序,如果沒有就等待子線程執行完成後結束程序。
import threading import time import random class MyThread(threading.Thread): def __init__(self, n): super(MyThread, self).__init__() self.n = n def run(self): print(‘task %s is operating‘ % self.n) t_time = random.randint(1, 8) time.sleep(t_time) # getNname獲取線程名稱 print(self.getName(), ‘finished‘, ‘I sleep %d seconds‘ % t_time) class Person(object): def __init__(self, name): self.name = name def get_info(self): # time.sleep(10) print(‘my name is %s.‘ % self.name) if __name__ == ‘__main__‘: start_time = time.time() for i in range(5): t = MyThread(i) t.start() # t.join() print(‘main thread finished.‘) print(‘*****執行另一個程序了******‘) p = Person(‘bigberg‘) p.get_info() print(‘*************************‘) print(‘cost: %s‘ % (time.time() - start_time))
結果:
# 線程開始執行 task 0 is operating task 1 is operating task 2 is operating task 3 is operating task 4 is operating # 主線程執行完畢 main thread finished. # 可以執行另一個程序了 *****執行另一個程序了****** my name is bigberg. ************************* # 這裏花費的時間是主線程的運行時間,顯然沒有計算子線程的時間 cost: 0.0019881725311279297 # 子線程在主線程結束後依然在運行 Thread-3 finished I sleep 2 seconds Thread-5 finished I sleep 2 seconds Thread-2 finished I sleep 3 seconds Thread-4 finished I sleep 4 seconds Thread-1 finished I sleep 5 seconds # 所有子線程完畢後程序結束 Process finished with exit code 0
二、join 等待子線程完成
如果在線程實例後加上join默認主線程是阻塞的,主線程會等待該子線程運行完成後在結束。
# -*- coding: UTF-8 -*- import threading import time import random class MyThread(threading.Thread): def __init__(self, n): super(MyThread, self).__init__() self.n = n def run(self): print(‘task %s is operating‘ % self.n) t_time = random.randint(1, 8) time.sleep(t_time) print(self.getName(), ‘finished‘, ‘I sleep %d seconds‘ % t_time) if __name__ == ‘__main__‘: start_time = time.time() for i in range(5): t = MyThread(i) t.start() t.join() # 添加join,阻塞主線程 print(‘main thread finished.‘) print(‘cost: %s‘ % (time.time() - start_time)) # 註 # 如果對每個線程都加join,那麽並發就沒有了,實際上線程都是串行的 # 前一個線程執行完了,才會執行下一個線程 # 主線程最後運行完畢
結果:
task 0 is operating Thread-1 finished I sleep 2 seconds task 1 is operating Thread-2 finished I sleep 6 seconds task 2 is operating Thread-3 finished I sleep 4 seconds task 3 is operating Thread-4 finished I sleep 8 seconds task 4 is operating Thread-5 finished I sleep 5 seconds # 這裏主線程已經是最後執行完畢的了 main thread finished. # 消耗的時間也是每個線程的運行時間之和 cost: 25.005265712738037
2.1 計算並發運行時間
如果不想計算出總的運行時間,而是所有線程的並發運行時間呢?就像上例中的那樣,最長運行時間是8秒,那麽所有線程都能在8秒內全部運行完畢。
把t.join()單獨移到for循環外面是不行的,因為這樣並發運行總會在最後一個線程出阻塞。如下:
# -*- coding: UTF-8 -*- import threading import time import random class MyThread(threading.Thread): def __init__(self, n): super(MyThread, self).__init__() self.n = n def run(self): print(‘task %s is operating‘ % self.n) t_time = random.randint(1, 8) time.sleep(t_time) print(self.getName(), ‘finished‘, ‘I sleep %d seconds‘ % t_time) if __name__ == ‘__main__‘: start_time = time.time() for i in range(5): t = MyThread(i) t.start() t.join() # 添加join,阻塞主線程 print(‘main thread finished.‘) print(‘cost: %s‘ % (time.time() - start_time))
結果:
task 0 is operating task 1 is operating task 2 is operating task 3 is operating task 4 is operating Thread-1 finished I sleep 2 seconds Thread-3 finished I sleep 2 seconds Thread-5 finished I sleep 3 seconds # 其實是在線程5,也就是最後一個線程出阻塞的 main thread finished. cost: 3.001293659210205 Thread-2 finished I sleep 4 seconds Thread-4 finished I sleep 5 seconds
正確的方法,定義一個空列表,獲取所以的線程實例,for 循環阻塞所有的線程實例
# -*- coding: UTF-8 -*- import threading import time import random class MyThread(threading.Thread): def __init__(self, n): super(MyThread, self).__init__() self.n = n def run(self): print(‘task %s is operating‘ % self.n) t_time = random.randint(1, 8) time.sleep(t_time) print(self.getName(), ‘finished‘, ‘I sleep %d seconds‘ % t_time) if __name__ == ‘__main__‘: t_list = [] start_time = time.time() for i in range(5): t = MyThread(i) t.start() t_list.append(t) for t in t_list: t.join() print(‘main thread finished.‘) print(‘cost: %s‘ % (time.time() - start_time))
結果,事實上也符合我們剛才的推論,運行時間最長的線程所消耗的時間,就是總的並發時間
task 0 is operating task 1 is operating task 2 is operating task 3 is operating task 4 is operating Thread-3 finished I sleep 3 seconds Thread-5 finished I sleep 3 seconds Thread-2 finished I sleep 7 seconds Thread-1 finished I sleep 7 seconds Thread-4 finished I sleep 8 seconds main thread finished. cost: 8.001787185668945 # 並發時間在8秒左右
總結:主線程創建一個子線程後,如果子線程調用join()方法,主線程會在調用的地方等待,直到該子線程運行完成才會接著往下執行。
三、守護線程setDaemon
setDaemon()方法:在主線程中創建子線程,該子線程調用setDaemon方法後成為主線程的守護線程。這種情況下如果主線程執行結束,那麽不管子線程是否完成,一並和主線程退出。這裏基本和join()方法相反。此外,還有個要特別註意的:必須在start() 方法調用之前設置,如果不設置為守護線程,程序會被無限掛起。
# -*- coding: UTF-8 -*- import threading import time import random class MyThread(threading.Thread): def __init__(self, n): super(MyThread, self).__init__() self.n = n def run(self): print(‘task %s is operating‘ % self.n) t_time = random.randint(1, 8) time.sleep(t_time) print(self.getName(), ‘finished‘, ‘I sleep %d seconds‘ % t_time) if __name__ == ‘__main__‘: start_time = time.time() for i in range(5): t = MyThread(i) t.setDaemon(True) t.start() print(‘main thread finished.‘, threading.current_thread(), threading.active_count()) print(‘cost: %s‘ % (time.time() - start_time))
註:threading.current_thread()查看當前運行的線程
threading.active_count() 查看活躍線程數
線程數 = 主線程 + 子線程數
結果:
task 0 is operating task 1 is operating task 2 is operating task 3 is operating task 4 is operating main thread finished. <_MainThread(MainThread, started 8656)> 6 cost: 0.0009999275207519531 Process finished with exit code 0 # 很顯然把子線程設置為主線程的守護線程後,主線程一旦結束,程序就執行退出運行,不會再等待子線程運行。
註:如果程序中有其他非守護線程時,還是會等待非守護線程運行完畢,程序才會結束。
join和 Daemon守護線程