python極簡教程04:進程和執行緒

測試奇譚,BUG不見。

大家好,我是譚叔。

這一場,主講python的進程和執行緒

目的:掌握初學必須的進程和執行緒知識。

進程和執行緒的區別和聯繫

終於開始加深難度,來到進程和執行緒的知識點~

單就這兩個概念,就難倒過不少初學者——今天學了概念,明天就忘記;明天學了例子,又忘記了概念。

要理解進程和執行緒的聯繫和區別,我舉個特簡單的例子:

你的電腦有兩個瀏覽器,一個Google瀏覽器,一個qq瀏覽器。

一個瀏覽器就是一個進程。

然後,你打開了Google瀏覽器,百度搜索了測試奇譚,又新開一個標籤頁,打開譚叔的文章,如下圖所示:

image-20210404220106555

你可以這樣理解——在同一個瀏覽器打開的兩個網頁就是兩個執行緒如果我關閉了瀏覽器,這兩個執行緒也沒有了。

好了,當你有了概念後,我才好講兩者的區別和聯繫。

進程

  • 優點

    • 穩定性高,當程式崩潰後,不影響其他進程的使用。即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?