Process 與 Thread

Process(行程、進程、處理程序)與 Thread(執行緒、線程)是作業系統中相當重要的概念。因為他們相對比較抽象,且通常 PHP 開發者對於兩者的概念較薄弱,但是在 Swoole 開發中會運用大量 ProcessThread 的觀念,所以在開始學 Swoole 之前對於他們必須有基本的了解。

Process

Process 是一個程式執行後實體化的概念,在分時系統年代中 Process 是程式運作的基本單位。一個程式可以產生多個 Process(一對多關係),若干 Process 有時可能與同一個程式相關聯,且每個 Process 皆可以同步(循序)或非同步(平行)的方式獨立執行。Process 需要一些資源才能完成工作,如:CPU、記憶體、檔案以及 I/O 裝置,且為依序逐一進行,也就是每個 CPU 核心任何時間內僅能執行一個 Process

Windows 系統當中可以在工作管理員當中來檢視,如:audiodg.exeCISVC.exe等都是一個個 Process
windows-process

Unix 系統中則可以透過 ps 命令來查看。

ps aux | grep php
Albert  1976  0.0.0.0  2511500  992 s000  ... php artisan socket:serve
Albert  1975  0.0.0.0  2511500 1188 s000  ... php artisan socket:serve
Albert  1974  0.0.0.0  2503180  724 s000  ... php artisan socket:serve

Process 中還另外包含了 ParentChild 的概念,Process 可以透過系統中的 fork 函數來產生自己的 Child Process,而所產生出來的 Child Process 會從 Parent Process 中複製一份相同的資源出來。如:Process A 同時 fork 出 Process BProcess C,假使 Process A 中宣告了 $a = 1Process BProcess C 中也會各有一份一樣的 $a = 1,但是不同 Process 間的變數修改是不會相互干擾的,也就是說假使今天在 Process A 中更改了 $a = 2Process BProcess C 的變數依然還會是 1。
process-fork

簡單來說,每個 Process 所擁有的資源在執行期間是獨立的,不與其它 Process 共用,所以每個 Process 只能存取屬於自己的部份,不能直接去更改其它 Process 的資料。

Thread

Thread 是作業系統中能夠進行運算排程的最小單位,Thread 被包含在 Process 中,在現代作業系統中,被設計為 Process 中的實際運作單位。也就是一個 Process 中可以包含一個或多個 Thread。在此不考慮 Hyper-Threading 的狀況下,通常一個 CPU 在同一時間只能提供一個 Thread 讓系統調用去執行程式。

ProcessThread 之間的關係我們可以把它想像成是工廠與員工的關係,工廠擁有資源與設備,但需要由員工去操作才能生產產品。所以要做出一件成品,工廠內至少要有一位員工在做事,不同的 Process 就像是不同的工廠,而 Thread 就像是一位派遣員工一樣,由作業系統負責調度他在什麼時間該去那家工廠上班,如果電腦擁有多個處理核心,即代表系統可以同時調用多個員工。

多任務的實現

試想一下,如果我們今天要使系統能夠同時執行多個任務該如何處理?根據上文的理解,我們可以:

  • 啟動多個 Process
  • 啟動一個 Process,並在該 Process 中啟動多個 Thread
  • 啟動多個 Process,並在每個 Process 中啟動多個 Thread

Linux的 fork 函數通過系统調用即可實現建立一個與原來 Process 幾乎相同的 Process。對於多任務,通常我們會設計 Master-Worker 模式,即一個 Master Process 負責分配任務,多個 Worker Process 負責執行任務。同理,如果是 Multi-Thread,Master 就是Master Thread,Worker 就是 Child Thread

Multi-Process 與 Multi-Thread

Multi-Process 的優點就是穩定度相當高,如果其中一個 Process 掛掉了,不會影響到其他的 Process。而對於 Multi-Thread,因為所有的 Thread 都共享同一個 Process 的資源,如果某一個 Thread 掛了,那這個 Process 幾乎就崩溃了。

在性能方面,不論是 Process 還是 Thread,不是啟用越多,效能就會越高,如果啟用數目太多,將會為 CPU 的產生資源調度的問題,因為 Process 或者 Thread 間的切換,本身就非常耗費系統資源。當啟用數量達到一定程度的时候,CPU 和記憶體會被大量消耗,反而會拖累效能甚至使整個系統當掉。

Multi-ThreadMulti-Process 相比更加輕量一些,而且 Thread 之間是可以互相共享資源的,所以不同 Thread 之間的交互就比較容易實現。而 Multi-Process 之間的通信,則需透過共享記憶體或是 Message Queue 等較複雜的方式才可實現。

Swoole 的架構模型

Swoole Server 中有以下幾種角色,分別是:Master(Process)、Reactor(Thread)、Manager(Process)、Worker(Process) 和 TaskWoker(Process)

swoole-structure

Master(Process)

Master 是 Swoole 的主要 Process,當我們啟動 Swoole 時,當下的 Process 就會成為 Master Process 並負責建立 Main Reactor、建立和管理 Reactor、建立 Manager 並開始接受客戶端請求等工作。

首先 Master 會透過 fork 函數建立一個 Manager,接著建立 Reactor,當全部建立完成後會呼叫 onStartcallback function,此時可以在這個 callback function 中對 Master 做一些處理或修改。如:將 Master 重新命名,保存 Master 的 PID 檔案等。

這裡要特別注意,因為 Master 不應該存在任何商業邏輯,因此 Swoole 底層會禁止你在此處做一些行為,如:發送請求、呼叫 Swoole 的異步 API 等等。

Reactor(Thread)

ReactorMulti-Thread 的方式執行,Reactor 底層由 epoll 函數來實現(在Mac系統是透過 Kqueue),用於實際監聽和處理來自客戶端的 Connect 請求,並完全是透過異步、非阻塞的模式來運作。

Manager(Process)

Swoole 中 Worker/Task worker 都是由 Managerfork 並管理的。當 Manager 被建立時,會呼叫 onManagerStartcallback function 通知上層的應用。

當底下的某個 Worker 意外結束執行時,Manager 會負責回收的工作,並同時建立新的 Worker 補足固定的數量維持 Swoole 的正常運作。

Manager 退出時,會呼叫 onManagerStopcallback function,可以利用此時進行一些回收邏輯。

Worker(Process)

WorkerMulti-Process 的方式執行,是 Swoole 中執行大部分邏輯的地方,因此在 Worker 中所對應的 callback function 數量也是最多的。

Worker 啟動後會呼叫 onWorkerStartcallback function,在這裡我們就能夠使用全部 Swoole 所提供的 API 了。

當上層的 Reactor 收到客戶端的請求後,就會將數據打包發送給 Worker,並在相對應的 callback function 中(如:onReceiveonRequestonMessage等)處理這些資料,並可以將處理結果回傳給 Reactor,再回傳至客戶端中。如果 Worker 正常退出,會呼叫 onWorkerStopcallback function,若是處理數據過程中出現嚴重錯誤或者 Worker 請求達到處理上限時,則會呼叫 onWorkerErrorcallback function 並結束該 Worker

Task Worker(Process)

Task Worker 一樣以 Multi-Process 的方式執行,只接受由 Worker 中透過 swoole_server->task/taskwait 方法指派過來的任務,並將結果回傳給 Worker

Process 間的通訊

在 Swoole 中,Process 間的通訊可以分為以下幾三種狀況:

  • Master <=> Worker
  • Worker <=> Worker
  • Worker <=> Task Worker

前兩種情形,在 Swoole 底層統一使用 Unix Socket 進行通訊,這些 socket 也都歸併到各自 ProcessReactor 中進行管理。而第三種情況,除了預設的 Unix Socket 外,還可以使用由系統提供的 Message Queue 来實作。

跨 Process 通訊

若你有不同 Worker Process 間需要共用變數的需求,基本上你有幾個方案:

  • 使用 apcu, swoole table, yac, shmop 等共享記憶體的擴展
  • 使用資料庫 redis, memcache, mysql
  • 使用檔案系統 /dev/shm, /tmp 等記憶體檔案