首頁 > 軟體

深入瞭解Python的多執行緒基礎

2021-11-25 19:01:34

執行緒

執行緒(Thread),有時也被稱為輕量級程序(Lightweight Process,LWP),是作業系統獨⽴排程和分派的基本單位,本質上就是一串指令的集合。

⼀個標準的執行緒由執行緒id、當前指令指標(PC),暫存器集合和堆疊組成,它是程序中的⼀個實體,執行緒本身不擁有系統資源,只擁有⼀點⼉在運⾏中必不可少的資源(如程式計數器、暫存器、棧),但它可與同屬⼀個程序的其它執行緒共用程序所擁有的全部資源。執行緒不能夠獨⽴執⾏,必須依存在程序中。

多執行緒

多執行緒就是使用多個執行緒同時執行任務,實現了任務的並行執行,從而提高程式執行效率的方法。

試想一下,如果在單個執行緒內執行多個任務(比如傳送網路請求等),如果前面的任務比較耗時,而後面的任務需要等待前面的任務執行完才能執行,這樣會影響任務執行效率,那麼就可以使用多執行緒去執行這些任務,任務可以同時進行,那麼將大大的提高執行效率。

Python多執行緒

在Python中,提供了threading模組來實現多程序操作,這個模組是基於較低階的模組 _thread 的基礎上建立的,提供了更易用的高階多執行緒API。

建立執行緒

可以通過threading模組中的Thread類來建立執行緒物件。

Thread語法結構:

threading.Thread(group, target, name, daemon)

  • group:預設為None(該引數是為了以後實現ThreadGroup類而保留的)
  • target:在run方法中呼叫的可呼叫物件,即執行緒要執行的任務
  • name:執行緒名稱,可以不設定,預設為"Thread-N"形式的名稱
  • args:給target指定的函數傳遞的引數,以元組的⽅式傳遞
  • kwargs:給target指定的函數傳遞命名引數
  • daemon:預設為None,將顯式地設定該執行緒是否為守護模式。如果是None,執行緒將繼承當前執行緒的守護模式屬性

Thread常用方法

  • start():啟動執行緒,並呼叫該執行緒中的run()方法
  • run():執行緒啟動時執行的方法,正是它去呼叫target指定的函數
  • join(timeout=None):讓當前呼叫者執行緒(一般為主執行緒)等待,直到該執行緒結束,timeout是可選的超時時間
  • is_alive():返回當前執行緒是否存活
import threading
import time
def work(i):
    print("子執行緒'{}'work正在執行......".format(threading.current_thread().name))
    time.sleep(i)
    print("子執行緒'{}'執行結束......".format(threading.current_thread().name))
if __name__ == '__main__':
    print("主執行緒{}啟動".format(threading.current_thread().name))
    # 獲取執行緒的名稱
    threads = []
    for i in range(5):
        t = threading.Thread(target=work, args=(i,))
    # 啟動執行緒
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print("主執行緒結束")

執行結果為:

上述程式碼中使用t.join()的功能就是讓主執行緒等待所有子執行緒結束後才結束,如果想設定守護執行緒(主執行緒結束,子執行緒也隨之結束,無論任務執行完成與否)的話,可以使用t.daemon = True

GIL鎖

GIL的全稱是Global Interpreter Lock(全域性直譯器鎖),這個鎖最初的設計是為了保證同一份資料不能被多個執行緒同時修改,每個執行緒在執行任務的時候都需要先獲取GIL,保證同一時刻只有一個執行緒可以執行,即同一時刻只有一個執行緒在直譯器中執行,因此Python中的多執行緒是假的多執行緒,不是真正意義上的多執行緒。 如果程式中有多個執行緒執行任務,那麼多個執行緒會被直譯器輪流執行,只不過是切換的很快、很頻繁,給人一種多執行緒「同時」在執行的錯覺。

執行緒池

在之前的文章說過,程序有程序池的機制,同樣,執行緒也有執行緒池。執行緒池可以在程式啟動時就建立自定義數量的空閒的執行緒,程式只要將一個任務提交給執行緒池,執行緒池就會啟動一個空閒的執行緒來執行它。當該任務執行結束後,該執行緒並不會死亡,而是再次返回到執行緒池中變成空閒狀態,等待下一個任務的執行。

multiprocessing.dummy裡面也有一個Pool物件,它其實就是執行緒的封裝,使用起來和multiprocessing的Pool非常類似。它們api都是通用的,簡單地說,multiprocessing.dummymultiprocessing程序池模組複製的一個執行緒池模組,強調一下,這裡執行緒池也是受到GIL限制的。

使用方式和multiprocessing.Pool一致,具體參考Python程序池。

from multiprocessing.dummy import Pool
import time
def work(i):
    print("work'{}'執行中......".format(i))
    time.sleep(2)
    print("work'{}'執行完畢......".format(i))
if __name__ == '__main__':
    # 建立執行緒池
    # Pool(5) 表示建立容量為5個執行緒的執行緒池
    pool = Pool(5)
    for i in range(10):
        pool.apply_async(work, (i, ))
    pool.close()
    pool.join()

總結

由於Python中的多執行緒受GIL鎖的限制,導致不能利用機器多核的特性,只能利用單核,是假的多執行緒,但是也不是一無是處,對於IO密集型任務,多執行緒是能夠有效提升執行效率的,這是因為單執行緒下有IO操作時,會進行IO等待,這樣會浪費等待的這段時間,而開啟多執行緒能線上程A等待時,自動切換到執行緒B,可以減少不必要的時間浪費,從而能提升程式執行效率,但是也不是最好的選擇,對於處理IO密集型任務,在Python還有更好的選擇協程,在後續文章會介紹。

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!


IT145.com E-mail:sddin#qq.com