工作中經常涉及到加速程序的運行,除了代碼邏輯的優化,算法的優化之外,還經常使用的一招就是并發編程。至于python的并型編程這一塊。說到并行編程,我們不得不談線程和進程這兩個概念:
- 進程:對于操作系統來說,一個任務就是一個進程(Process),熟悉linux的朋友敲命令ps -aux 就可以看到本機正在啟動的任務——進程 。
- 線程:在一個進程內部(一個任務),要同時干多件事,就需要同時運行多個“子任務”,我們把進程內的這些“子任務”稱為線程(Thread)。
這里要注意的是每個進程至少要干一個任務,每個進程至少有一個線程。
正常的程序都是順序執行,你完成干完一件事后再接著干下一件事。這樣就會出現一個問題,無法同時干多件事。而并行編程就是希望程序能夠同時干多件事情,起到程序加速運行的效果。
并行編程的三種模式
- 1.多進程: 開啟多個進程,每個進程中都有一個線程,并行去執行多個任務。
- 2.多線程 :只開啟一個進程,在進程中采取多線程編程模式,真正的多線程是將任務分發到不同的CPU,充分利用多核CPU。
- 3.多進程加多線程:這個就是上面兩種的組合,開啟多個進程,每個進程中都采用多個進程去合力完成多個任務。
這里我們就來好好解釋一下,python的GIL機制:python的GIL本質是一把互斥鎖,保證同一時間只有一條線程訪問解釋器級別的數據,這樣就避免了數據競爭帶來的混亂,但是這個機制使得原本希望多線程帶來的并行執行,變成了串行執行。
如果是I/O密集型操作,比如訪問web服務,訪問數據庫等時,由于這些操作不涉及到CPU的運算,所以此時多線程就能夠發揮優勢,多線程可以同時進行多個I/O操作,加速程序運行。
而CPU密集型操作,要頻繁使用CPU計算的場景,python中的多線程則幾乎完全變成了串行,加之還要在不同線程中間切換,有時效果還不如順序執行。此時就需要使用多進程來加速程序運行。
實驗部分
導入multiprocessing.dummy ——python中多線程模塊,threading擁有同樣功能;multiprocessing——python中多進程模塊。
import requests
import time
from multiprocessing.dummy import Pool as ThreadPool
from multiprocessing import Pool
tpool = ThreadPool()
ppool = Pool()
I/O密集型任務測試
在I/O密集型任務上分布測試 順序執行,多線程,多進程的速度如何:
urls = ["https://www.baidu.com/"]*100
time_1 = time.time()
for i in urls:
requests.get("https://www.baidu.com/")
time_2 = time.time()
print("I/O密集型:for 循環使用時間",time_2-time_1)
time_3 = time.time()
tpool.map(requests.get,urls)
time_4 = time.time()
print("I/O密集型:多線程使用時間",time_4-time_3)
time_5 = time.time()
ppool.map(requests.get,urls)
time_6 = time.time()
print("I/O密集型:多進程使用時間",time_6-time_5)
結果如下:
I/O密集型:for 循環使用時間 14.102440595626831
I/O密集型:多線程使用時間 2.5032284259796143
I/O密集型:多進程使用時間 2.267827272415161
多線程和多進程確實比順序執行快了將近6倍,而多進程和多線程的速度差不多。
CPU(計算)密集型任務測試
在CPU(計算)密集型任務上分布測試 順序執行,多線程,多進程的速度如何:
data = [10000]*10
def get_jiecheng(num):
res = 1
for i in range(num):
res *= (i+1)
time_7 = time.time()
for i in data:
get_jiecheng(i)
time_8 = time.time()
print("計算密集型:for 循環使用時間",time_8-time_7)
time_9 = time.time()
tpool.map(get_jiecheng,data)
time_10 = time.time()
print("計算密集型:多線程使用時間",time_10 - time_9)
time_11 = time.time()
ppool.map(get_jiecheng,data)
time_12 = time.time()
print("計算密集型:多進程使用時間",time_12-time_11)
time_13 = time.time()
ppool.map_async(get_jiecheng,data)
time_14 = time.time()
print("計算密集型:多進程異步使用時間",time_14-time_13)
計算密集型:for 循環使用時間 0.5359704494476318
計算密集型:多線程使用時間 0.5580940246582031
計算密集型:多進程使用時間 0.10313701629638672
計算密集型:多進程異步使用時間 0.00018835067749023438
此時多線程反而變成慢了,多進程比多線程和順序執行快了大概5倍左右,同時,異步的多進程最快,加速5000多倍。但是異步的缺點也顯而易見,就是無法實現進程之間的通信。
python的多進程在linux服務器上存在在一個內存復制機制——子進程會復制父進程的狀態(內存空間數據等),所以如果主進程耗的資源較多時,就會造成大量的不必要的內存復制,從而導致內存爆掉。
總結
綜上python 多進程和多線程總結如下:
- 多線程的缺點:CPU密集型計算速度變慢。
- 多線程的優點:I/O密集型計算加速效果明顯,不是特別消耗CPU和內存資源。
- 多進程的缺點:特別消耗耗CPU和內存資源
- 多進程的優點:I/O密集型和CPU密集型計算加速效果明顯。
所以筆者建議以后碰到I/O密集型計算建議使用 python多線程,而CPU密集型計算建議使用python多進程。
參考
https://www.liaoxuefeng.com/wiki/897692888725344/923056118147584
https://blog.csdn.net/qq_33733970/article/details/77988642