執行緒與並行模型

執行緒(thread)

想像一間忙碌的廚房,只有一位廚師照著一份食譜工作。他一次只能做一個步驟,所以煮湯的時候沙拉只能擺著沒人動。現在想像同一間廚房、同一份食譜、同一個食材櫃、同一個爐子,但改成三位廚師,他們都讀同一份食譜、共用同一個食材櫃,而每位廚師各自帶著一張小便條紙,記著自己做到哪一步,三個人於是並肩往前推進。一個執行緒就是其中一位廚師:它是一條單一、循序的執行流,活在一個正在執行的程式裡,並且和程式裡其他的執行緒共享幾乎所有東西。

更精確地說,執行緒是作業系統真正能排上 CPU 去執行的最小單位。同一個行程(process)裡的每個執行緒共享該行程的程式碼(指令)、全域資料、堆積(heap,程式執行時動態配置記憶體的池子),以及已開啟的檔案等資源。但每個執行緒都有自己私有的部分:自己的堆疊(stack,存放區域變數和當下這條流正在進行的函式呼叫鏈)、自己的一組 CPU 暫存器,以及自己的程式計數器,也就是指向這條流下一個要執行指令的指標。所以所有執行緒看到的是同一塊共享記憶體,但每個都記得自己做到哪裡。當作業系統把 CPU 從一個執行緒切換到另一個時,就是存下這一小份私有狀態、再載入下一個執行緒的。

為什麼這很重要:因為執行緒共享記憶體,兩個執行緒只要寫入和讀取同一個變數,就能互相傳遞資料,不需要複製、也不需要進出核心。這種共享正是執行緒強大的原因,也正是它危險的原因。如果兩個執行緒在沒有協調的情況下同時動到同一個變數,就會產生競爭條件(race condition),程式可能算出錯誤或隨機的結果。讓這種共享變安全(鎖、號誌等)是「同步」的工作,是另一個主題;執行緒本身給你的是並行能力,不是安全保證。

一個網頁瀏覽器在同一個行程裡用上好幾個執行緒:一個執行緒讓視窗對你的點擊與捲動保持反應,另一個下載一張大圖,第三個執行頁面的 JavaScript。因為它們共享同一塊記憶體,下載執行緒可以把下載好的圖片直接放進顯示執行緒會讀取的緩衝區,完全不需要在程式之間複製資料。

一個行程、多個執行緒共享記憶體,這既是速度的來源,也是危險的來源。

執行緒不是獨立的程式,它活在某個行程裡,也隨著該行程結束而消失。執行緒之間不會自動互相保護;共享記憶體很方便,但未經同步的共享會造成競爭。

又稱
thread of executionlightweight process輕量行程控制流