python 多线程中的 join 和 daemon
文章目錄
- 第一關(guān):簡單的 join()
- 第二關(guān):join(timeout)
- 第三關(guān):setDaemon(True)
第一關(guān):簡單的 join()
import threading import timedef run():time.sleep(2)print('當(dāng)前線程的名字是: ', threading.current_thread().name)time.sleep(2)if __name__ == '__main__':print('這是主線程:', threading.current_thread().name)thread_list = []for i in range(5):t = threading.Thread(target=run)thread_list.append(t)for t in thread_list:t.start()start_time = time.time()thread_list[0].join()print('一共用時(shí):', time.time() - start_time)print('主線程結(jié)束!', threading.current_thread().name)執(zhí)行結(jié)果是:
這是主線程: MainThread 當(dāng)前線程的名字是: Thread-1 當(dāng)前線程的名字是: Thread-5當(dāng)前線程的名字是: Thread-4當(dāng)前線程的名字是: 當(dāng)前線程的名字是: Thread-2 Thread-3 一共用時(shí): 4.014104843139648 主線程結(jié)束! MainThread為了弄清楚 join() 的作用,我關(guān)注代碼 23 行的用時(shí)。
第 23 行,主線程阻塞,直到 thread_list[0] 退出后才往下執(zhí)行。
看打印結(jié)果第 7 行,用時(shí) 4s,這是好理解的,因?yàn)?thread_list[0] 大概用 4s。說明主線程確實(shí)阻塞了,一直阻塞到 thread_list[0] 執(zhí)行完畢。
我們把代碼修改一下,把 run() 函數(shù)的 sleep 時(shí)間增加。
def run():time.sleep(5)print('當(dāng)前線程的名字是: ', threading.current_thread().name)time.sleep(5)預(yù)期結(jié)果是主線稱大約阻塞 10s
執(zhí)行結(jié)果是:
這是主線程: MainThread 當(dāng)前線程的名字是: 當(dāng)前線程的名字是: Thread-5當(dāng)前線程的名字是: Thread-1當(dāng)前線程的名字是: Thread-3 當(dāng)前線程的名字是: Thread-2Thread-4一共用時(shí): 10.009864091873169 主線程結(jié)束! MainThread和預(yù)期相符。
如果把 run 函數(shù)改成死循環(huán),比如
def run():while(True):time.sleep(2)print('當(dāng)前線程的名字是: ', threading.current_thread().name)time.sleep(2)那么主線程就會(huì)一直阻塞,根本不會(huì)打印 “一共用時(shí):…”,而是一直打印 “當(dāng)前線程的名字是:…”
這也印證了主線程確實(shí)阻塞了,等待子線程退出,如果不退出就一直阻塞。
第二關(guān):join(timeout)
在上面的例子中,如果子線程不返回,父線程就一直阻塞。如果需求是父線程阻塞一段時(shí)間,時(shí)間到了以后,就算子線程不返回,父線程也可以繼續(xù)向下執(zhí)行,那么我們可以給 join() 傳入一個(gè)時(shí)間參數(shù)。意思是最多等待這么多,等不到我就繼續(xù)往下執(zhí)行了。
實(shí)驗(yàn)代碼是:
import threading import timedef run():time.sleep(2)print('當(dāng)前線程的名字是: ', threading.current_thread().name)time.sleep(2)if __name__ == '__main__':print('這是主線程:', threading.current_thread().name)thread_list = []for i in range(5):t = threading.Thread(target=run)thread_list.append(t)for t in thread_list:t.start()start_time = time.time()thread_list[0].join(2)print('一共用時(shí):', time.time() - start_time)print('主線程結(jié)束!', threading.current_thread().name)第 23 行:父線程最多等待 2s,等不到就繼續(xù)執(zhí)行
打印結(jié)果是:
這是主線程: MainThread 一共用時(shí): 2.0001044273376465 主線程結(jié)束! MainThread 當(dāng)前線程的名字是: 當(dāng)前線程的名字是: 當(dāng)前線程的名字是: Thread-2 Thread-3 Thread-1 當(dāng)前線程的名字是: Thread-4 當(dāng)前線程的名字是: Thread-5可以看到,父線程確實(shí)只阻塞了 2s。需要強(qiáng)調(diào)的是,主線程阻塞,不會(huì)對(duì)子線程造成任何影響,子線程依然執(zhí)行自己的代碼,根本不存在什么“主線程等待一段時(shí)間然后殺死子線程”,這是謬論。
當(dāng)然,如果你傳入?yún)?shù)是 10,主線程不一定非要等 10s,如果子線程執(zhí)行了 3s 就返回了,那么主線程會(huì)立刻往下執(zhí)行,而不是繼續(xù)等待 7s。
例如把代碼第 23 行改成
thread_list[0].join(6)執(zhí)行結(jié)果是:
這是主線程: MainThread 當(dāng)前線程的名字是: 當(dāng)前線程的名字是: Thread-2 Thread-3當(dāng)前線程的名字是: 當(dāng)前線程的名字是: 當(dāng)前線程的名字是: Thread-1Thread-4 Thread-5 一共用時(shí): 4.028920888900757 主線程結(jié)束! MainThread第三關(guān):setDaemon(True)
先上代碼:
import threading import timedef run():time.sleep(2)print('當(dāng)前線程的名字是: ', threading.current_thread().name)time.sleep(2)if __name__ == '__main__':print('這是主線程:', threading.current_thread().name)thread_list = []for i in range(5):t = threading.Thread(target=run)t.setDaemon(True)thread_list.append(t)for t in thread_list:t.start()start_time = time.time()print('一共用時(shí):', time.time() - start_time)print('主線程結(jié)束!', threading.current_thread().name)和之前不一樣的是第 17 行,多加了 t.setDaemon(True),這句話的意思是把線程的 daemon 屬性設(shè)成 True。daemon 有守護(hù)神的意思,也可以說把 t 設(shè)置為守護(hù)線程,守護(hù)誰呢?守護(hù)父線程嗎?咱們后面會(huì)分析。
網(wǎng)上充斥著這樣的解釋:如果一個(gè)子線程的 daemon 被設(shè)為 True,那么父線程結(jié)束了,這個(gè)子線程就立刻被干掉。是不是這樣呢?我們看執(zhí)行結(jié)果:
這是主線程: MainThread 一共用時(shí): 0.0 主線程結(jié)束! MainThread似乎是這樣,主線程一執(zhí)行完,子線程就被殺死了,連打印都來不及。
我們把 15-18 行代碼改一下:
for i in range(5):t = threading.Thread(target=run)# t.setDaemon(True)thread_list.append(t)thread_list[0].setDaemon(True)也就是說,僅僅設(shè)置 thread_list[0] 的 daemon 為 True。預(yù)期結(jié)果是主線程一結(jié)束,thread_list[0] 就被殺死了,其余的子線程會(huì)繼續(xù)執(zhí)行。
結(jié)果是:
這是主線程: MainThread 一共用時(shí): 0.0 主線程結(jié)束! MainThread 當(dāng)前線程的名字是: Thread-3當(dāng)前線程的名字是: Thread-2當(dāng)前線程的名字是: 當(dāng)前線程的名字是: Thread-5 當(dāng)前線程的名字是: Thread-4Thread-1這就奇怪了,不是 Thread-1 被殺死了嗎,怎么后面還有打印呢?
肯定是我們的理解有問題。實(shí)際上,官方是這樣解釋 daemon 的:
A thread can be flagged as a “daemon thread”. The significance of this flag is that the entire Python program exits when only daemon threads are left.
以上摘自 https://docs.python.org/3/library/threading.html,黑體字是我加的。
也就是說,如果父線程執(zhí)行完畢,剩下的都是守護(hù)線程,那么這些守護(hù)線程也不用再執(zhí)行了,直接強(qiáng)制退出;如果父線程執(zhí)行完畢,剩下了 3 個(gè)守護(hù)線程和 1 個(gè)非守護(hù)線程,那么這 3 個(gè)守護(hù)線程還會(huì)繼續(xù)執(zhí)行,當(dāng)非守護(hù)線程結(jié)束的時(shí)候,這些守護(hù)線程才會(huì)強(qiáng)制退出。
我們做個(gè)實(shí)驗(yàn):
import threading import timedef run(cnt):while cnt:time.sleep(1)print('當(dāng)前線程的名字是: ', threading.current_thread().name)time.sleep(1)cnt = cnt - 1print(threading.current_thread().name, ' 退出')if __name__ == '__main__':print('這是主線程:', threading.current_thread().name)thread_list = []cnt_list = [2, 4, 4, 4, 4]for i in range(5):t = threading.Thread(target=run, args=(cnt_list[i],))t.setDaemon(True)thread_list.append(t)thread_list[0].setDaemon(False)for t in thread_list:t.start()start_time = time.time()print('一共用時(shí):', time.time() - start_time)print('主線程結(jié)束!', threading.current_thread().name)5-11:每個(gè)子線程執(zhí)行時(shí)間不一樣,根據(jù)傳參 cnt 定
第 16 行,增加了cnt_list = [2, 4, 4, 4, 4],意圖是讓 thread_list[0] 執(zhí)行時(shí)間短一些,僅執(zhí)行 while 循環(huán) 2 次,其他子線程執(zhí)行 4 次
18-23:先把所有子線程設(shè)為守護(hù)線程,后面再把 thread_list[0] 設(shè)為非守護(hù)線程(偷懶了)
輸出結(jié)果:
這是主線程: MainThread 一共用時(shí): 0.0 主線程結(jié)束! MainThread 當(dāng)前線程的名字是: 當(dāng)前線程的名字是: 當(dāng)前線程的名字是: Thread-5 當(dāng)前線程的名字是: Thread-2 Thread-1 Thread-4 當(dāng)前線程的名字是: Thread-3 當(dāng)前線程的名字是: Thread-1當(dāng)前線程的名字是: 當(dāng)前線程的名字是: 當(dāng)前線程的名字是: Thread-3 當(dāng)前線程的名字是: Thread-5Thread-4 Thread-2Thread-1 退出可以發(fā)現(xiàn),父線程退出后,守護(hù)線程并沒有退出,當(dāng)非守護(hù)線程 Thread-1 退出后,其他守護(hù)線程強(qiáng)制退出。
由此可見,守護(hù)線程不僅僅守護(hù)父線程,也會(huì)守護(hù)其他非守護(hù)線程。
以上就是本文的全部內(nèi)容。歡迎各位老師批評(píng)斧正。
總結(jié)
以上是生活随笔為你收集整理的python 多线程中的 join 和 daemon的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 页面放在哪_思维制胜!PPT内容巨多的页
- 下一篇: 用python实现AES加密解密