python極簡教程04:進程和執行緒
測試奇譚,BUG不見。
大家好,我是譚叔。
這一場,主講python的進程和執行緒。
目的:掌握初學必須的進程和執行緒知識。
進程和執行緒的區別和聯繫
終於開始加深難度,來到進程和執行緒的知識點~
單就這兩個概念,就難倒過不少初學者——今天學了概念,明天就忘記;明天學了例子,又忘記了概念。
要理解進程和執行緒的聯繫和區別,我舉個特簡單的例子:
你的電腦有兩個瀏覽器,一個Google瀏覽器,一個qq瀏覽器。
一個瀏覽器就是一個進程。
然後,你打開了Google瀏覽器,百度搜索了測試奇譚,又新開一個標籤頁,打開譚叔的文章,如下圖所示:
你可以這樣理解——在同一個瀏覽器打開的兩個網頁就是兩個執行緒
。如果我關閉了瀏覽器,這兩個執行緒也沒有了。
好了,當你有了概念後,我才好講兩者的區別和聯繫。
進程
-
優點
穩定性高
,當程式崩潰後,不影響其他進程的使用。即Google瀏覽器崩潰後,qq瀏覽器還可以正常使用。
-
缺點
創建進程的代價大
。即當你開了各種各樣的瀏覽器後,你的電腦會特別卡,因為電腦記憶體和CPU有上限。
執行緒
-
優點
- 多執行緒通常比多進程快一點,但優勢不大
-
缺點
- 任何一個執行緒掛掉會造成整個進程崩潰,因為執行緒共享進程的記憶體(瀏覽器的例子不再適用,可以理解為綁在一條船上的螞蚱)
多進程
因為大多數小夥伴用的Windows作業系統,所以針對Unix/Linux的fork()調用拋開不談。
在python,一般使用multiprocessing實現多進程。
import os
from multiprocessing import Process
def run_proc(name):
print('開始執行子進程 %s (%s)…' % (name, os.getpid()))
# 子進程要執行的程式碼
if __name__ == '__main__':
print('父進程 %s.' % os.getpid())
p = Process(target=run_proc, args=('test',)) # 創建一個Process實例
print('子進程即將開始.')
p.start() # 用start()方法啟動實例
p.join() # join()方法可以等待子進程結束後再繼續往下運行,通常用於進程間的同步
print('子進程結束.')
要細講嗎?
其實,我的備註寫得很全,你只需copy程式碼下來執行一次,或者debug一次,就能明白。
執行緒池
如何理解?
即程式碼可運行的進程數量,有個地方管控它。
from multiprocessing import Pool
import os
import time
import random
def long_time_task(name):
print('開始 task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s 執行花費 %0.2f 秒.' % (name, (end - start)))
if __name__ == '__main__':
print('父親進程 %s.' % os.getpid())
p = Pool(3)
# 因為Pool的默認大小是4,故task 0,1,2,3是立刻執行的,而task 4要等待前面某個task完成後才執行,但最多同時執行4個進程
# 由於Pool的默認大小是CPU的核數,如果你擁有8核CPU,要提交至少9個子進程才能看到等待效果
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('等待子進程結束...')
p.close()
# 對Pool對象調用join()方法會等待所有子進程執行完畢
# 調用join()之前必須先調用close(),調用close()之後就不能繼續添加新的Process了
p.join()
print('所有子進程已執行.')
以上,是必知必會的。
當然,最好的學習時機是:當你要用時,再來複盤學,效果最佳。
如果你學了,沒有使用場景,我建議緩一緩學或者作為知識儲備。
多執行緒
多執行緒有三點必須提前明確:
- 多任務需求可以由多進程完成,也可以由一個進程內的多執行緒完成
- 進程是由若干執行緒組成
- 一個進程至少有一個執行緒
Python的標準庫提供了兩個模組:_thread和threading,_thread是低級模組,我們一般使用高級模組threading(對_thread進行過封裝)。
啟動一個執行緒就是把一個函數傳入並創建Thread實例,然後調用start()開始執行,我們看一個簡單的例子:
import time
import threading
# 新執行緒執行的程式碼
def loop():
# 由於任何進程默認就會啟動一個執行緒,我們把該執行緒稱為主執行緒,主執行緒又可以啟動新的執行緒,
# Python的threading模組有個current_thread()函數,它永遠返回當前執行緒的實例
print('執行緒ss %s 運行中…' % threading.current_thread().name)
n = 0
# 主執行緒實例的名字叫MainThread,子執行緒的名字在創建時指定,我們用LoopThread命名子執行緒,在列印輸出的時候顯示名字
while n < 5:
n = n + 1
print('執行緒ss %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('執行緒ss %s 已結束.' % threading.current_thread().name)
print('執行緒 %s is 運行中…' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
'''
對於 join()方法而言,其另一個重要方面是其實它根本不需要調用。
一旦執行緒啟動,它們 就會一直執行,直到給定的函數完成後退出。
如果主執行緒還有其他事情要去做,而不是等待這些執行緒完成(例如其他處理或者等待新的客戶端請求),
就可以不調用 join()。join()方法只有在你需要等待執行緒完成的時候才是有用的
'''
print('執行緒 %s 已結束.' % threading.current_thread().name)
同樣,以上內容,是必知必會的。
並且,工作場景,我們一般會使用多執行緒處理問題,而非多進程。(注意:是一般)
至於進程間通訊、執行緒鎖、GIL鎖、多執行緒變數、執行緒間通訊、非同步協程等知識,講起來比較複雜,也不是極簡教程的核心,你可以先自學,或者當你真正要使用它的時候再去看,再去學。
一如既往,做個總結
01 多執行緒、多進程,是必知必會的;
02 學習進度自己琢磨,既不能死磕,亦不能簡單跳過;
03 小夥伴比較關心,面試時會不會被問到。答:一般公司不會,so?