多執行緒

Jinku Hu 2023年1月30日
  1. Python 多執行緒
  2. 執行緒間通訊
多執行緒

Python 的多執行緒執行,不是真正的多執行緒同時執行,這是因為 Python 直譯器引入的 GIL-全域性直譯器鎖帶來的制約。Python 可以有多執行緒,但是每個執行緒執行的時候都需要等待 GIL 的釋放,然後獲得 GIL 後上鎖,在該執行緒執行的時間內,其他執行緒無法執行,因為 GIL 被鎖住,等該執行緒釋放 GIL 後,其他執行緒就可以執行了。所以,不管 CPU 有幾核,在某一個時間只有一個執行緒在執行,這就是為什麼 Python 的多執行緒有時候被詬病為假多執行緒的原因。

但是,Python 多執行緒還是有其用武之地的,就是在 I/O 密集型的應用裡面,I/O 的讀取速度是個瓶頸,可以一個 I/O 讀取執行緒,一個其他執行緒,這樣就可以利用等待 I/O 的時間內,並行的進行其他資料處理。

假如程式是計算密集型而不是 I/O 密集型,Python 多執行緒在效能上沒有本質的提高。

Python 多執行緒

建立執行緒 threading.Thread

使用 threading 模組,可以通過建立新的 threading.Thread 併為其分配要執行的函式來啟動新的執行執行緒:

import threading


def Test():
    print "Hello threading!"


my_thread = threading.Thread(target=Test)

target 引數引用要執行的函式(或可呼叫物件)。在 Thread 物件呼叫 start 之前,執行緒不會開始執行。

開始執行緒 Thread.start()

my_thread.start()  # prints 'Hello threading!'

現在 my_thread 已經執行並終止,start 再次呼叫將產生一個 RuntimeError。如果你想將你的執行緒作為守護程序執行,需要設定 daemon=True,或者在呼叫之前設定 my_thread.daemonTrue,這就會使的該執行緒在後臺作為守護程序靜默執行。

加入執行緒 Thread.join()

如果你將一個大的任務分成幾個小任務並希望同時執行它們,但需要等待所有這些工作完成之後,繼續進行下面的任務,那麼 Thread.join() 就是你正在尋找的方法。

例如,假設你要下載網站的多個頁面並將其編譯為單個頁面。你可以這樣做:

import requests
from threading import Thread
from queue import Queue

q = Queue(maxsize=20)


def put_page_to_q(page_num):
    q.put(requests.get("http://some-website.com/page_%s.html" % page_num))


def compile(q):
    # magic function that needs all pages before being able to be executed
    if not q.full():
        raise ValueError
    else:
        print("Done compiling!")


threads = []
for page_num in range(20):
    t = Thread(target=requests.get, args=(page_num,))
    t.start()
    threads.append(t)

# Next, join all threads to make sure all threads are done running before
# we continue. join() is a blocking call (unless specified otherwise using
# the kwarg blocking=False when calling join)
for t in threads:
    t.join()

# Call compile() now, since all threads have completed
compile(q)

建立自定義執行緒類

使用 threading.Thread 類我們可以子類化新的自定義 Thread 類。我們必須覆蓋子類中的 run 方法。

from threading import Thread
import time


class Sleepy(Thread):
    def run(self):
        time.sleep(5)
        print("Hello form Thread")


if __name__ == "__main__":
    t = Sleepy()
    t.start()  # start method automatic call Thread class run method.
    # print 'The main program continues to run in foreground.'
    t.join()
    print("The main program continues to run in the foreground.")

你看到的輸出順序時這樣的

Hello form Thread
The main program continues to run in the foreground.

但假如將 t.join() 註釋掉的話,輸出順序就變為了

The main program continues to run in the foreground.
Hello form Thread

這也驗證了我們上面結束的 join 方法時等所有的執行緒執行結束後,才會繼續下面的任務。

執行緒間通訊

假如程式碼中有多個執行緒,有一些資料需要在它們間共享,那麼我們需要在它們之間進行安全的通訊。這需要使用 Queuequeue 型別。

from queue import Queue
from threading import Thread

# create a data producer


def producer(output_queue):
    while True:
        data = data_computation()

        output_queue.put(data)


# create a consumer
def consumer(input_queue):
    while True:
        # retrieve data (blocking)
        data = input_queue.get()

        # do something with the data

        # indicate data has been consumed
        input_queue.task_done()

使用共享佇列建立生產者和消費者執行緒,

q = Queue()
t1 = Thread(target=consumer, args=(q,))
t2 = Thread(target=producer, args=(q,))
t1.start()
t2.start()
作者: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

DelftStack.com 創辦人。Jinku 在機器人和汽車行業工作了8多年。他在自動測試、遠端測試及從耐久性測試中創建報告時磨練了自己的程式設計技能。他擁有電氣/ 電子工程背景,但他也擴展了自己的興趣到嵌入式電子、嵌入式程式設計以及前端和後端程式設計。

LinkedIn Facebook