python多線程并發(fā)實(shí)例及其優(yōu)化
單線程執(zhí)行
python的內(nèi)置模塊提供了兩個(gè)內(nèi)置模塊:thread和threading,thread是源生模塊,threading是擴(kuò)展模塊,在thread的基礎(chǔ)上進(jìn)行了封裝及改進(jìn)。所以只需要使用threading這個(gè)模塊就能完成并發(fā)的測(cè)試
實(shí)例
創(chuàng)建并啟動(dòng)一個(gè)單線程
import threading def myTestFunc(): print("我是一個(gè)函數(shù)") t = threading.Thread(target=myTestFunc) # 創(chuàng)建一個(gè)線程 t.start() # 啟動(dòng)線程
執(zhí)行結(jié)果
C:\Python36\python.exe D:/MyThreading/myThread.py 我是一個(gè)線程函數(shù) Process finished with exit code 0
其實(shí)單線程的執(zhí)行結(jié)果和單獨(dú)執(zhí)行某一個(gè)或者某一組函數(shù)結(jié)果是一樣的,區(qū)別只在于用線程的方式執(zhí)行函數(shù),而線程是可以同時(shí)執(zhí)行多個(gè)的,函數(shù)是不可以同時(shí)執(zhí)行的。
多線程執(zhí)行
上面介紹了單線程如何使用,多線程只需要通過(guò)循環(huán)創(chuàng)建多個(gè)線程,并循環(huán)啟動(dòng)線程執(zhí)行就可以了
實(shí)例
import threading from datetime import datetime def thread_func(): # 線程函數(shù) print('我是一個(gè)線程函數(shù)', datetime.now()) def many_thread(): threads = [] for _ in range(10): # 循環(huán)創(chuàng)建10個(gè)線程 t = threading.Thread(target=thread_func) threads.append(t) for t in threads: # 循環(huán)啟動(dòng)10個(gè)線程 t.start() if __name__ == '__main__': many_thread()
執(zhí)行結(jié)果
C:\Python36\python.exe D:/MyThreading/manythread.py 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.205146 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.205146 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.206159 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.206159 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.206159 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.207139 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.207139 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.207139 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.208150 我是一個(gè)線程函數(shù) 2019-06-23 16:54:58.208150 Process finished with exit code 0
通過(guò)循環(huán)創(chuàng)建10個(gè)線程,并且執(zhí)行了10次線程函數(shù),但需要注意的是python的并發(fā)并非絕對(duì)意義上的同時(shí)處理,因?yàn)閱?dòng)線程是通過(guò)循環(huán)啟動(dòng)的,還是有先后順序的,通過(guò)執(zhí)行結(jié)果的時(shí)間可以看出還是有細(xì)微的差異,但可以忽略不記。當(dāng)然如果線程過(guò)多就會(huì)擴(kuò)大這種差異。我們啟動(dòng)500個(gè)線程看下程序執(zhí)行時(shí)間
實(shí)例
import threading from datetime import datetime def thread_func(): # 線程函數(shù) print('我是一個(gè)線程函數(shù)', datetime.now()) def many_thread(): threads = [] for _ in range(500): # 循環(huán)創(chuàng)建500個(gè)線程 t = threading.Thread(target=thread_func) threads.append(t) for t in threads: # 循環(huán)啟動(dòng)500個(gè)線程 t.start() if __name__ == '__main__': start = datetime.today().now() many_thread() duration = datetime.today().now() - start print(duration)
執(zhí)行結(jié)果
0:00:00.111657 Process finished with exit code 0
500個(gè)線程共執(zhí)行了大約0.11秒
那么針對(duì)這種問(wèn)題我們?cè)撊绾蝺?yōu)化呢?我們可以創(chuàng)建25個(gè)線程,每個(gè)線程執(zhí)行20次線程函數(shù),這樣在啟動(dòng)下一個(gè)線程的時(shí)候,上一個(gè)線程已經(jīng)在循環(huán)執(zhí)行了,這樣就大大減少了并發(fā)的時(shí)間差異
優(yōu)化
import threading from datetime import datetime def thread_func(): # 線程函數(shù) print('我是一個(gè)線程函數(shù)', datetime.now()) def execute_func(): for _ in range(20): thread_func() def many_thread(): start = datetime.now() threads = [] for _ in range(25): # 循環(huán)創(chuàng)建500個(gè)線程 t = threading.Thread(target=execute_func) threads.append(t) for t in threads: # 循環(huán)啟動(dòng)500個(gè)線程 t.start() duration = datetime.now() - start print(duration) if __name__ == '__main__': many_thread()
輸出結(jié)果(僅看程序執(zhí)行間隔)
0:00:00.014959 Process finished with exit code 0
后面的優(yōu)化執(zhí)行500次并發(fā)一共花了0.014秒。比未優(yōu)化前的500個(gè)并發(fā)快了幾倍,如果線程函數(shù)的執(zhí)行時(shí)間比較長(zhǎng)的話,那么這個(gè)差異會(huì)更加顯著,所以大量的并發(fā)測(cè)試建議使用后者,后者比較接近同時(shí)“并發(fā)”
守護(hù)線程
多線程還有一個(gè)重要概念就是守護(hù)線程。那么在這之前我們需要知道主線程和子線程的區(qū)別,之前創(chuàng)建的線程其實(shí)都是main()線程的子線程,即先啟動(dòng)主線程main(),然后執(zhí)行線程函數(shù)子線程。
那么什么是守護(hù)線程?即當(dāng)主線程執(zhí)行完畢之后,所有的子線程也被關(guān)閉(無(wú)論子線程是否執(zhí)行完成)。默認(rèn)不設(shè)置的情況下是沒(méi)有守護(hù)線程的,主線程執(zhí)行完畢后,會(huì)等待子線程全部執(zhí)行完畢,才會(huì)關(guān)閉結(jié)束程序。
但是這樣會(huì)有一個(gè)弊端,當(dāng)子線程死循環(huán)了或者一直處于等待之中,則程序?qū)⒉粫?huì)被關(guān)閉,被被無(wú)限掛起,我們把上述的線程函數(shù)改成循環(huán)10次, 并睡眠2秒,這樣效果會(huì)更明顯
import threading from datetime import datetime import time def thread_func(): # 線程函數(shù) time.sleep(2) i = 0 while(i < 11): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 循環(huán)創(chuàng)建500個(gè)線程 t = threading.Thread(target=thread_func) threads.append(t) for t in threads: # 循環(huán)啟動(dòng)500個(gè)線程 t.start() if __name__ == '__main__': many_thread() print("thread end")
執(zhí)行結(jié)果
C:\Python36\python.exe D:/MyThreading/manythread.py thread end 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.468612 2019-06-23 19:08:00.469559 2019-06-23 19:08:00.469559 2019-06-23 19:08:00.469559 2019-06-23 19:08:00.469559 2019-06-23 19:08:00.469559 2019-06-23 19:08:00.469559 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.470556 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.471554 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.472557 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.473548 2019-06-23 19:08:00.474545 2019-06-23 19:08:00.474545 2019-06-23 19:08:00.474545 2019-06-23 19:08:00.474545 2019-06-23 19:08:00.474545 2019-06-23 19:08:00.474545 2019-06-23 19:08:00.474545 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.475552 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.476548 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 2019-06-23 19:08:00.477546 Process finished with exit code 0
根據(jù)上述結(jié)果可以看到主線程打印了“thread end”之后(主線程結(jié)束),子線程還在繼續(xù)執(zhí)行,并未隨著主線程的結(jié)束而結(jié)束
下面我們通過(guò) setDaemon方法給子線程添加守護(hù)線程,我們把循環(huán)改為死循環(huán),再來(lái)看看輸出結(jié)果(注意守護(hù)線程要加在start之前)
import threading from datetime import datetime def thread_func(): # 線程函數(shù) i = 0 while(1): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 循環(huán)創(chuàng)建500個(gè)線程 t = threading.Thread(target=thread_func) threads.append(t) t.setDaemon(True) # 給每個(gè)子線程添加守護(hù)線程 for t in threads: # 循環(huán)啟動(dòng)500個(gè)線程 t.start() if __name__ == '__main__': many_thread() print("thread end")
輸出結(jié)果
2019-06-23 19:12:35.564539 2019-06-23 19:12:35.564539 2019-06-23 19:12:35.564539 2019-06-23 19:12:35.564539 2019-06-23 19:12:35.564539 2019-06-23 19:12:35.564539 2019-06-23 19:12:35.565529 2019-06-23 19:12:35.565529 2019-06-23 19:12:35.565529 thread end Process finished with exit code 0
通過(guò)結(jié)果我們可以發(fā)現(xiàn),主線程關(guān)閉之后子線程也會(huì)隨著關(guān)閉,并沒(méi)有無(wú)限的循環(huán)下去,這就像程序執(zhí)行到一半強(qiáng)制關(guān)閉執(zhí)行一樣,看似暴力卻很有用,如果子線程發(fā)送一個(gè)請(qǐng)求未收到請(qǐng)求結(jié)果,那不可能永遠(yuǎn)等下去,這時(shí)候就需要強(qiáng)制關(guān)閉。所以守護(hù)線程解決了主線程和子線程關(guān)閉的問(wèn)題。
阻塞線程
上面說(shuō)了守護(hù)線程的作用,那么有沒(méi)有別的方法來(lái)解決上述問(wèn)題呢? 其實(shí)是有的,那就是阻塞線程,這種方式更加合理,使用join()方法阻塞線程,讓主線程等待子線程執(zhí)行完成之后再往下執(zhí)行,再關(guān)閉所有子線程,而不是只要主線程結(jié)束,不管子線程是否執(zhí)行完成都終止子線程執(zhí)行。下面我們給子線程添加上join()(主要join要加到start之后)
import threading from datetime import datetime import time def thread_func(): # 線程函數(shù) time.sleep(1) i = 0 while(i < 11): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 循環(huán)創(chuàng)建500個(gè)線程 t = threading.Thread(target=thread_func) threads.append(t) t.setDaemon(True) # 給每個(gè)子線程添加守護(hù)線程 for t in threads: # 循環(huán)啟動(dòng)500個(gè)線程 t.start() for t in threads: t.join() # 阻塞線程 if __name__ == '__main__': many_thread() print("thread end")
執(zhí)行結(jié)果
程序會(huì)一直執(zhí)行,但是不會(huì)打印“thread end”語(yǔ)句,因?yàn)樽泳€程并未結(jié)束,那么主線程就會(huì)一直等待。
疑問(wèn):有人會(huì)覺(jué)得這和什么都不設(shè)置是一樣的,其實(shí)會(huì)有一點(diǎn)區(qū)別的,從守護(hù)線程和線程阻塞的定義就可以看出來(lái),如果什么都沒(méi)設(shè)置,那么主線程會(huì)先執(zhí)行完畢打印后面的“thread end”,而等待子線程執(zhí)行完畢。兩個(gè)都設(shè)置了,那么主線程會(huì)等待子線程執(zhí)行結(jié)束再繼續(xù)執(zhí)行。
而對(duì)于死循環(huán)或者一直等待的情況,我們可以給join設(shè)置超時(shí)等待,我們?cè)O(shè)置join的參數(shù)為2,那么子線程會(huì)告訴主線程讓其等待2秒,如果2秒內(nèi)子線程執(zhí)行結(jié)束主線程就繼續(xù)往下執(zhí)行,如果2秒內(nèi)子線程未結(jié)束,主線程也會(huì)繼續(xù)往下執(zhí)行,執(zhí)行完成后關(guān)閉子線程
輸出結(jié)果
import threading from datetime import datetime import time def thread_func(): # 線程函數(shù) time.sleep(1) i = 0 while(1): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 循環(huán)創(chuàng)建500個(gè)線程 t = threading.Thread(target=thread_func) threads.append(t) t.setDaemon(True) # 給每個(gè)子線程添加守護(hù)線程 for t in threads: # 循環(huán)啟動(dòng)500個(gè)線程 t.start() for t in threads: t.join(2) # 設(shè)置子線程超時(shí)2秒 if __name__ == '__main__': many_thread() print("thread end")
你運(yùn)行程序后會(huì)發(fā)現(xiàn),運(yùn)行了大概2秒的時(shí)候,程序會(huì)數(shù)據(jù)“thread end” 然后結(jié)束程序執(zhí)行, 這就是阻塞線程的意義,控制子線程和主線程的執(zhí)行順序
總結(jié)
最好呢,再次說(shuō)一下守護(hù)線程和阻塞線程的定義
- 守護(hù)線程:子線程會(huì)隨著主線程的結(jié)束而結(jié)束,無(wú)論子線程是否執(zhí)行完畢
- 阻塞線程:主線程會(huì)等待子線程的執(zhí)行結(jié)束,才繼續(xù)執(zhí)行
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python實(shí)現(xiàn)線程池之線程安全隊(duì)列
這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)線程池之線程安全隊(duì)列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05分析總結(jié)Python數(shù)據(jù)化運(yùn)營(yíng)KMeans聚類
本文主要以 Python 使用 Keans 進(jìn)行聚類分析的簡(jiǎn)單舉例應(yīng)用介紹聚類分析,它是探索性數(shù)據(jù)挖掘的主要任務(wù),也是統(tǒng)計(jì)數(shù)據(jù)分析的常用技術(shù),用于許多領(lǐng)域2021-08-08python實(shí)現(xiàn)126郵箱發(fā)送郵件
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)126郵箱發(fā)送郵件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05Python如何將給定字符串中的大寫(xiě)英文字母按以下對(duì)應(yīng)規(guī)則替換
這篇文章主要介紹了Python如何將給定字符串中的大寫(xiě)英文字母按以下對(duì)應(yīng)規(guī)則替換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10python計(jì)數(shù)排序和基數(shù)排序算法實(shí)例
這篇文章主要介紹了python計(jì)數(shù)排序和基數(shù)排序算法實(shí)例,需要的朋友可以參考下2014-04-04django數(shù)據(jù)模型on_delete, db_constraint的使用詳解
這篇文章主要介紹了django數(shù)據(jù)模型on_delete, db_constraint的使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12cython加速python代碼的方法實(shí)現(xiàn)
本文主要介紹了cython加速python代碼的方法實(shí)現(xiàn),特別是在涉及到數(shù)值計(jì)算密集型任務(wù)時(shí),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07TensorFlow搭建神經(jīng)網(wǎng)絡(luò)最佳實(shí)踐
這篇文章主要為大家詳細(xì)介紹了TensorFlow搭建神經(jīng)網(wǎng)絡(luò)最佳實(shí)踐,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03