python 程式、執行緒 (二)

starkbl發表於2021-09-09

一、多執行緒與多程式的對比

在中簡單的說過,CPython中的GIL使得同一時刻只能有一個執行緒執行,即併發執行。並且即使是多核CPU,GIL使得同一個程式中的多個執行緒也無法對映到多個CPU上執行,這麼做最初是為了安全著想,慢慢的也成為了限制CPython效能的問題。
就像是一個執行緒想要執行,就必須得到GIL,否則就不能拿到CPU資源。但是也不是說一個執行緒在拿到CPU資源後就一勞永逸,在執行的過程中GIL可能會釋放並被其他執行緒獲取,所以說其它的執行緒會與本執行緒競爭CPU資源。
在中有關於GIL釋放和GIL的概要。
多執行緒在python2中:當一個執行緒進行I/O的時候會釋放鎖,另外當ticks計數達到100(ticks可以看作是Python自身的一個計數器,也可對比著位元組碼指令理解,專門做用於GIL,每次釋放後歸零,這個計數可以透過 sys.setcheckinterval 來調整)。鎖釋放之後,就涉及到執行緒的排程,執行緒的鎖進行,執行緒的切換。這是會消耗CPU資源,因此會造成程式效能問題和等待時延。特別是在CPU密集型程式碼時。
但是對於多程式,GIL就無法限制,多個程式可以再多個CPU上執行,充分利用多核優勢。事情往往是相對的,雖然可以充分利用多核優勢,但是程式之間的切換卻比執行緒的切換代價更高。
所以選擇多執行緒還是多程式,主要還是看怎樣權衡代價,什麼樣的情況。

1、CPU密集程式碼

下面來利用斐波那契數列模擬CPU密集運算。

def fib(n):    # 求斐波那契數列的第n個值
    if n<=2:        return 1
    return fib(n-1)+fib(n-2)

<1>、多程式

列印第25到35個斐波那契數,並計算程式執行時間

import timefrom concurrent.futures import ThreadPoolExecutor, as_completedfrom concurrent.futures import ProcessPoolExecutordef fib(n):
    if n<=2:        return 1
    return fib(n-1)+fib(n-2)if __name__ == "__main__":    with ProcessPoolExecutor(3) as executor:  # 使用程式池控制  每次執行3個程式
        all_task = [executor.submit(fib, (num)) for num in range(25,35)]
        start_time = time.time()        for future in as_completed(all_task):
            data = future.result()
            print("exe result: {}".format(data))

        print("last time is: {}".format(time.time()-start_time))# 輸出exe result: 75025exe result: 121393exe result: 196418exe result: 317811exe result: 514229exe result: 832040exe result: 1346269exe result: 2178309exe result: 3524578exe result: 5702887last time is: 4.457437038421631

輸出結果,每次列印三個exe result,總重列印十個結果,多程式執行時間為4.45秒

<2>、多執行緒

import timefrom concurrent.futures import ThreadPoolExecutor, as_completedfrom concurrent.futures import ProcessPoolExecutordef fib(n):
    if n<=2:        return 1
    return fib(n-1)+fib(n-2)if __name__ == "__main__":    with ThreadPoolExecutor(3) as executor:  # 使用執行緒池控制  每次執行3個執行緒
        all_task = [executor.submit(fib, (num)) for num in range(25,35)]
        start_time = time.time()        for future in as_completed(all_task):
            data = future.result()
            print("exe result: {}".format(data))

        print("last time is: {}".format(time.time()-start_time))# 輸出exe result: 121393exe result: 75025exe result: 196418exe result: 317811exe result: 514229exe result: 832040exe result: 1346269exe result: 2178309exe result: 3524578exe result: 5702887last time is: 7.3467772006988525

最終程式執行時間為7.34秒

程式的執行之間與計算機的效能有關,每天計算機的執行時間都會有差異。從上述結果中看顯然多執行緒比多程式要耗費時間。這就是因為對於密集程式碼(密集運算,迴圈語句等),tick計數很快達到100,GIL來回的釋放競爭,執行緒之間頻繁切換,所以對於密集程式碼的執行中,多執行緒效能不如對程式。

2、I/O密集程式碼

一個執行緒在I/O阻塞的時候,會釋放GIL,掛起,然後其他的執行緒會競爭CPU資源,涉及到執行緒的切換,但是這種代價與較高時延的I/O來說是不足為道的。
下面用sleep函式模擬密集I/O

def random_sleep(n):
    time.sleep(n)    return n

<1>、 多程式

def random_sleep(n):    time.sleep(n)    return nif __name__ == "__main__":
    with ProcessPoolExecutor(5) as executor:
        all_task = [executor.submit(random_sleep, (num)) for num in [2]*30]
        start_time = time.time()        for future in as_completed(all_task):
            data = future.result()            print("exe result: {}".format(data))        print("last time is: {}".format(time.time()-start_time))
#  輸出
exe result: 2exe result: 2......(30個)
exe result: 2exe result: 2last time is: 12.412866353988647

每次列印5個結果,總共二十個列印結果,多程式執行時間為12.41秒

<2>、多執行緒

def random_sleep(n):    time.sleep(n)    return nif __name__ == "__main__":
    with ThreadPoolExecutor(5) as executor:
        all_task = [executor.submit(random_sleep, (num)) for num in [2]*30]
        start_time = time.time()        for future in as_completed(all_task):
            data = future.result()            print("exe result: {}".format(data))        print("last time is: {}".format(time.time()-start_time))

#  輸出
exe result: 2exe result: 2......(30個)
exe result: 2exe result: 2last time is: 12.004231214523315

I/O密集多執行緒情況下,程式的效能較多程式有了略微的提高。IO密集型程式碼(檔案處理、網路爬蟲等),多執行緒能夠有效提升效率(單執行緒下有IO操作會進行IO等待,造成不必要的時間浪費,而開啟多執行緒能線上程A等待時,自動切換到執行緒B,可以不浪費CPU的資源,從而能提升程式執行效率)。所以python的多執行緒對IO密集型程式碼比較友好

3、總結

  • CPU密集型程式碼(各種迴圈處理、計數等等),多執行緒效能不如多程式。

  • I/O密集型程式碼(檔案處理、網路爬蟲等),多程式不如多執行緒。

原文出處:https://www.cnblogs.com/welan/p/10003312.html  

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3705/viewspace-2817806/,如需轉載,請註明出處,否則將追究法律責任。

相關文章