亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

python多線程并發實例及其優化

系統 1749 0

單線程執行

python的內置模塊提供了兩個內置模塊:thread和threading,thread是源生模塊,threading是擴展模塊,在thread的基礎上進行了封裝及改進。所以只需要使用threading這個模塊就能完成并發的測試

實例

創建并啟動一個單線程

            
import threading
def myTestFunc():
print("我是一個函數")
t = threading.Thread(target=myTestFunc) # 創建一個線程
t.start() # 啟動線程
          

執行結果

            
C:\Python36\python.exe D:/MyThreading/myThread.py
我是一個線程函數
Process finished with exit code 0
          

其實單線程的執行結果和單獨執行某一個或者某一組函數結果是一樣的,區別只在于用線程的方式執行函數,而線程是可以同時執行多個的,函數是不可以同時執行的。

多線程執行

上面介紹了單線程如何使用,多線程只需要通過循環創建多個線程,并循環啟動線程執行就可以了

實例

            
import threading
from datetime import datetime
def thread_func(): # 線程函數
  print('我是一個線程函數', datetime.now())
def many_thread():
  threads = []
  for _ in range(10): # 循環創建10個線程
    t = threading.Thread(target=thread_func)
    threads.append(t)
  for t in threads: # 循環啟動10個線程
    t.start()
if __name__ == '__main__':
  many_thread()
          

執行結果

            
C:\Python36\python.exe D:/MyThreading/manythread.py
我是一個線程函數 2019-06-23 16:54:58.205146
我是一個線程函數 2019-06-23 16:54:58.205146
我是一個線程函數 2019-06-23 16:54:58.206159
我是一個線程函數 2019-06-23 16:54:58.206159
我是一個線程函數 2019-06-23 16:54:58.206159
我是一個線程函數 2019-06-23 16:54:58.207139
我是一個線程函數 2019-06-23 16:54:58.207139
我是一個線程函數 2019-06-23 16:54:58.207139
我是一個線程函數 2019-06-23 16:54:58.208150
我是一個線程函數 2019-06-23 16:54:58.208150
Process finished with exit code 0
          

通過循環創建10個線程,并且執行了10次線程函數,但需要注意的是python的并發并非絕對意義上的同時處理,因為啟動線程是通過循環啟動的,還是有先后順序的,通過執行結果的時間可以看出還是有細微的差異,但可以忽略不記。當然如果線程過多就會擴大這種差異。我們啟動500個線程看下程序執行時間

實例

            
import threading
from datetime import datetime
def thread_func(): # 線程函數
print('我是一個線程函數', datetime.now())
def many_thread():
threads = []
for _ in range(500): # 循環創建500個線程
t = threading.Thread(target=thread_func)
threads.append(t)
for t in threads: # 循環啟動500個線程
t.start()
if __name__ == '__main__':
start = datetime.today().now()
many_thread()
duration = datetime.today().now() - start
print(duration)
          

執行結果

            
0:00:00.111657
Process finished with exit code 0
          

500個線程共執行了大約0.11秒

那么針對這種問題我們該如何優化呢?我們可以創建25個線程,每個線程執行20次線程函數,這樣在啟動下一個線程的時候,上一個線程已經在循環執行了,這樣就大大減少了并發的時間差異

優化

            
import threading
from datetime import datetime
def thread_func(): # 線程函數
print('我是一個線程函數', datetime.now())
def execute_func():
for _ in range(20):
thread_func()
def many_thread():
start = datetime.now()
threads = []
for _ in range(25): # 循環創建500個線程
t = threading.Thread(target=execute_func)
threads.append(t)
for t in threads: # 循環啟動500個線程
t.start()
duration = datetime.now() - start
print(duration)
if __name__ == '__main__':
many_thread()
          

輸出結果 (僅看程序執行間隔)

            
0:00:00.014959
Process finished with exit code 0
          

后面的優化執行500次并發一共花了0.014秒。比未優化前的500個并發快了幾倍,如果線程函數的執行時間比較長的話,那么這個差異會更加顯著,所以大量的并發測試建議使用后者,后者比較接近同時“并發”

守護線程

多線程還有一個重要概念就是守護線程。那么在這之前我們需要知道主線程和子線程的區別,之前創建的線程其實都是main()線程的子線程,即先啟動主線程main(),然后執行線程函數子線程。

那么什么是守護線程?即當主線程執行完畢之后,所有的子線程也被關閉(無論子線程是否執行完成)。默認不設置的情況下是沒有守護線程的,主線程執行完畢后,會等待子線程全部執行完畢,才會關閉結束程序。

但是這樣會有一個弊端,當子線程死循環了或者一直處于等待之中,則程序將不會被關閉,被被無限掛起,我們把上述的線程函數改成循環10次, 并睡眠2秒,這樣效果會更明顯

            
import threading
from datetime import datetime
import time
def thread_func(): # 線程函數
   time.sleep(2)
i = 0
while(i < 11):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循環創建500個線程
t = threading.Thread(target=thread_func)
threads.append(t)
for t in threads: # 循環啟動500個線程
t.start()
if __name__ == '__main__':
many_thread()
print("thread end")
          

執行結果

            
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
          

根據上述結果可以看到主線程打印了“thread end”之后(主線程結束),子線程還在繼續執行,并未隨著主線程的結束而結束

下面我們通過 setDaemon方法給子線程添加守護線程,我們把循環改為死循環,再來看看輸出結果(注意守護線程要加在start之前)

            
import threading
from datetime import datetime
def thread_func(): # 線程函數
i = 0
while(1):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循環創建500個線程
t = threading.Thread(target=thread_func)
threads.append(t)
t.setDaemon(True) # 給每個子線程添加守護線程
for t in threads: # 循環啟動500個線程
t.start()

if __name__ == '__main__':
many_thread()
print("thread end")
          

輸出結果

            
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
          

通過結果我們可以發現,主線程關閉之后子線程也會隨著關閉,并沒有無限的循環下去,這就像程序執行到一半強制關閉執行一樣,看似暴力卻很有用,如果子線程發送一個請求未收到請求結果,那不可能永遠等下去,這時候就需要強制關閉。所以守護線程解決了主線程和子線程關閉的問題。

阻塞線程

上面說了守護線程的作用,那么有沒有別的方法來解決上述問題呢? 其實是有的,那就是阻塞線程,這種方式更加合理,使用join()方法阻塞線程,讓主線程等待子線程執行完成之后再往下執行,再關閉所有子線程,而不是只要主線程結束,不管子線程是否執行完成都終止子線程執行。下面我們給子線程添加上join()(主要join要加到start之后)

            
import threading
from datetime import datetime
import time
def thread_func(): # 線程函數
time.sleep(1)
i = 0
while(i < 11):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循環創建500個線程
t = threading.Thread(target=thread_func)
threads.append(t)
t.setDaemon(True) # 給每個子線程添加守護線程
for t in threads: # 循環啟動500個線程
t.start()
for t in threads:
t.join() # 阻塞線程
if __name__ == '__main__':
many_thread()
print("thread end")
          

執行結果

程序會一直執行,但是不會打印“thread end”語句,因為子線程并未結束,那么主線程就會一直等待。

疑問:有人會覺得這和什么都不設置是一樣的,其實會有一點區別的,從守護線程和線程阻塞的定義就可以看出來,如果什么都沒設置,那么主線程會先執行完畢打印后面的“thread end”,而等待子線程執行完畢。兩個都設置了,那么主線程會等待子線程執行結束再繼續執行。

而對于死循環或者一直等待的情況,我們可以給join設置超時等待,我們設置join的參數為2,那么子線程會告訴主線程讓其等待2秒,如果2秒內子線程執行結束主線程就繼續往下執行,如果2秒內子線程未結束,主線程也會繼續往下執行,執行完成后關閉子線程

輸出結果

            
import threading
from datetime import datetime
import time
def thread_func(): # 線程函數
time.sleep(1)
i = 0
while(1):
print(datetime.now())
i += 1
def many_thread():
threads = []
for _ in range(10): # 循環創建500個線程
t = threading.Thread(target=thread_func)
threads.append(t)
t.setDaemon(True) # 給每個子線程添加守護線程
for t in threads: # 循環啟動500個線程
t.start()
for t in threads:
t.join(2) # 設置子線程超時2秒
if __name__ == '__main__':
many_thread()
print("thread end")
          

你運行程序后會發現,運行了大概2秒的時候,程序會數據“thread end” 然后結束程序執行, 這就是阻塞線程的意義,控制子線程和主線程的執行順序

總結

最好呢,再次說一下守護線程和阻塞線程的定義

  • 守護線程:子線程會隨著主線程的結束而結束,無論子線程是否執行完畢
  • 阻塞線程:主線程會等待子線程的執行結束,才繼續執行

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 成人影院免费在线观看 | 亚洲 欧美 中文字幕 | 日本一级毛一级毛片短视频 | 99精品国产在这里白浆 | 天天操天天摸天天爽 | 97在线观免费视频观看 | 免费播放一区二区三区 | 成人网久久 | 老司机午夜精品视频播放 | 国产在线拍国产拍拍偷 | 国产亚洲精品一区二区 | 久久天天操 | 亚洲欧美激情精品一区二区 | 日韩精品一区二区三区免费视频 | 久久久久国产精品免费看 | 久久精品国产精品青草 | 国产福利一区二区在线观看 | 亚洲福利一区福利三区 | 天天操天天添 | 国产精品嫩草影院奶水 | 成人午夜毛片在线看 | 亚洲精品综合一二三区在线 | 操综合 | 亚洲aⅴ久久久噜噜噜噜 | 国产一区二区三区久久精品 | 午夜社区 | 成年女人毛片免费视频 | 亚洲美女视频网站 | 99精品高清不卡在线观看 | 福利视频欧美一区二区三区 | 免费看一毛一级毛片视频 | 国产精品亚洲二线在线播放 | 中文字幕日本一区波多野不卡 | 亚洲精品美女国产一区 | 高清国产一区二区 | 干一干操一操 | 天天看夜夜操 | 91视频看| 久久亚洲不卡一区二区 | 精品无人乱码一区二区三区 | 亚洲第一成年免费网站 |