PHP 與其他程式語言一樣,都可以透過終端機輸入指令執行程式,這種方式簡單易用,但要在網頁開發中使用時,我們還需要安裝網頁伺服器。 PHP 本身並不響應實際的 HTTP 請求,那是網頁伺服器在做的事情,我們很難單純靠終端機執行 PHP 程式就完成網頁架設。 因此才需要安裝網頁伺服器 (例如:IIS、Apache、Nginx),並且進行設定,讓網頁伺服器將 HTTP 請求轉發給 PHP 處理,最後接收處理結果回傳給使用者。

您可以簡單理解成 PHP 只是用來產生 HTML 給網頁伺服器回傳給使用者,而在網頁伺服器與 PHP 之間進行溝通的東西,稱為伺服器應用程式接口 (Server Application Programming Interface, SAPI),您可能會在其他地方看到 ISAPI 或 NSAPI,其實都是差不多的東西。 PHP 常見的 SAPI 有 CLI、CGI、mod_php、FastCGI 及 PHP-FPM。

認識行程 (Process) 與執行緒 (Thread)

在了解各項 PHP SAPI 之前,需要先了解行程及執行緒兩個概念。

行程 (Process)
指電腦中已經在執行並且載入到記憶體中的程式,隨時都能被 CPU 執行,在工作管理員 (Windows) 或活動監控器 (macOS) 或 jobs (Linux) 可以看到電腦正在執行的所有行程。 每個行程在建立時,系統會分配一塊獨立的記憶體空間給行程使用,因此不同行程之間要共享資料較為困難。
執行緒 (Thread)
一個行程是由一個或多個執行緒組成,也就是說行程是執行緒的容器,執行緒是程式實際執行時的執行單位。在多執行緒中,每個執行緒隸屬同一個行程,也共用同樣的記憶體空間,所以多個執行緒之間要共享資料較為容易,可以想像為程式中的全域變數。當多個執行緒同時操作相同的資料時,必需確保執行緒安全,使程式功能正確完成。

在這裡不用詳細了解行程與執行緒的細節,只要知道兩個重點:

  1. 一個行程是由一個或多個執行緒組成。
  2. 在多執行緒運行時,需確保執行緒安全。

執行緒安全

那什麼是執行緒安全 (Thread Safe) 呢?執行緒安全是指某個函數同時被多個執行緒呼叫時,能夠正確地處理公用變數資料,使程式功能夠正確完成。來看看一段有問題的 Java 程式碼:

                
                    public class Main {
                        private static int count;

                        public static void increase() {
                            count++;
                        }

                        public static void main(String[] args) throws InterruptedException {
                            Thread thread1 = new Thread(() -> {
                                for (int i = 0; i < 100000; i++) {
                                    increase();
                                }
                            });
                            Thread thread2 = new Thread(() -> {
                                for (int i = 0; i < 100000; i++) {
                                    increase();
                                }
                            });

                            thread1.start();
                            thread2.start();

                            thread1.join();
                            thread2.join();

                            System.out.println(count);
                        }
                    }
                
            

以上程式碼,當兩個執行緒都執行完成後,理論上應該輸出 200000 才對,但實際狀況卻是每次執行答案都不一樣,這就是沒有執行緒安全造成的問題。 解決方式也很簡單,只要當 increase() 函數被呼叫時鎖定不讓其它執行緒呼叫即可,這就是執行緒安全的作法,但此方式會減慢執行速度,因此也不能將所有函數都加上鎖定,應謹慎評估使用。

執行方式 1:命令列介面 (CLI)

命令列介面(Command-Line Interface, CLI)是在圖形化介面之前最廣泛的使用者介面,絕大部分的程式語言都支援 CLI 介面執行程式,當然 PHP 也不例外。不過 CLI 在網頁開發用處不大,您很難單純依靠 CLI 指令完成網頁架設。

使用方式

直接開啟命令提示字元 (CMD) 輸入 php 加檔名即可。

                
                    php index.php
                
            

執行方式 2:通用閘道器介面 (CGI)

通用閘道器介面 (Common Gateway Interface, CGI) 是為網路服務提供生成動態內容的程式,CGI 在執行時,每處理一個 HTTP 請求都會產生一個行程 (Process) 來生成內容 (通常為 HTML),當 HTTP 請求處理完成後,行程便會關閉,有點類似執行了一次 CLI 指令。 該模式簡單容易實現,缺點就是執行速度慢,每次執行都需要開啟關閉一次行程。

使用方式

CGI 模式需要搭配網頁伺服器才能使用,以下使用 Apache 作為範例,打開 httpd.conf 設定檔,根據以下內容進行修改,修改完成後重新啟動服務。

  • Apache 安裝路徑:C:/Web/Apache24
  • PHP 安裝路徑:C:/Web/php
  • 網頁放置路徑:C:/Web/html
                
                    ServerRoot "C:/Web/Apache24"
                    DocumentRoot "C:/Web/html/"
                    <Directory "C:/Web/html/">
                        Options None
                        AllowOverride None
                        Require all granted
                    </Directory>

                    <Directory "C:/Web/php/">
                        Options None
                        AllowOverride None
                        Require all granted
                    </Directory>

                    <IfModule mime_module>
                        AddType application/x-httpd-php .php
                    </IfModule>

                    <IfModule alias_module>
                        ScriptAlias /cgi-bin/ "C:/Web/php/"
                    </IfModule>

                    Action application/x-httpd-php "/cgi-bin/php-cgi.exe"
                
            

執行方式 3:mod_php

把 PHP 整合至 Apache 中,變成 Apache 的一個模組,沒有使用額外的主行程 (Process) 來處理,通通由 Apache 主行程處理。 在處理大流量時,Apache 主行程會生成多個子行程同步處理,以提升處理速度,通常在 Apache 執行時,就已經生成一定數量的子行程,等待 HTTP 請求,避免像 CGI 模式需要頻繁的開關行程。生成子行程的方式還區分 prefork、worker、event 及 winnt 等不同模式,這些模式稱為多行程模式 (Multi-Processing Module, MPM)。

prefork 模式
多個子行程,每個子行程只有單一執行緒,同一時間只能處理一個 HTTP 請求,是一個成熟穩定的模式,也是唯一沒有執行緒安全問題的 MPM 模式,但此模式不擅長處理大流量。
worker 模式
多個子行程,每個子行程都是多執行緒,在大流量的狀況下,會比 prefork 模式擁有更多可用的執行緒,效能表現更加優秀。 該模式在使用 keep-alive 長連線時,會一直佔用某個執行緒,即使沒有產生任何的請求,也需要等到超時才會釋放執行緒,該問題在 prefork 模式也存在。
event 模式
多個子行程,每個子行程都是多執行緒,是 Apache 最新的 MPM 模式,與 worker 模式非常像,但解決了 keep-alive 長連線造成的問題。 該模式需要運行在較新的 Linux 平台,在缺乏良好執行緒設計的舊平台表現不太理想。
winnt 模式
一個子行程,多執行緒,是專門針對 Windows 設計的 MPM 模式。

這種方式的優點是執行速度比 CGI 快很多,但安全性較低,且 PHP 與 Apache 耦合度較高,無法將 PHP 處理程式與網頁伺服器分別架設在不同主機,早期 PHP 大多都使用這種模式。

確認 Apache MPM 模式

要查看目前 Apache 是使用哪一種 MPM 模式,可以執行以下指令,並找到 Server MPM。

                
                    // Windows 使用指令
                    httpd.exe -V
                        ... (省略) ...
                        Server MPM:     WinNT
                          threaded:     yes (fixed thread count)

                    // Linux / macOS 使用指令
                    httpd -V
                        ... (省略) ...
                        Server MPM:     prefork
                          threaded:     no
                
            

使用方式

mod_php 模式設定非常簡單,以下使用 Apache 作為範例,打開 httpd.conf 設定檔,根據以下內容進行修改,修改完成後重新啟動服務。

  • Apache 安裝路徑:C:/Web/Apache24
  • PHP 安裝路徑:C:/Web/php (需為執行緒安全版本)
  • 網頁放置路徑:C:/Web/html
                
                    ServerRoot "C:/Web/Apache24"
                    DocumentRoot "C:/Web/html/"
                    <Directory "C:/Web/html/">
                        Options None
                        AllowOverride None
                        Require all granted
                    </Directory>

                    LoadModule php_module C:/Web/php/php8apache2_4.dll
                    <FilesMatch \.php$>
                        SetHandler application/x-httpd-php
                    </FilesMatch>

                    # PHP 設定檔所在目錄
                    PHPIniDir "C:\Web\php"
                
            

執行方式 4:FastCGI

快速通用閘道器介面 (Fast Common Gateway Interface, FastCGI) 是 CGI 的增強版,改善了 CGI 在執行時,行程一開一關的問題,也改善了 mod_php 與網頁伺服器耦合度較高的問題。 FastCGI 執行速度不亞於 mod_php,同樣採用多行程常駐開啟方式,且每個行程使用單一執行緒,等待 HTTP 網址請求,更重要的是 FastCGI 與網頁伺服器可以分別架設在不同主機上,兩者再透過 TCP 進行溝通,這對大流量網站非常重要。 FastCGI 網頁伺服器耦合度較低,是目前在 Windows 執行 PHP 最快的模式,缺點則是設定較為複雜。

使用方式

在 Windows 使用 Apache 執行 FastCGI 模式有很多種方式,其中一種方式是使用 fcgid_module 模組,該模組在 Apache 2.4 需額外下載才能使用,且不支援 TCP 模式 (要支援 TCP 需改用 mod_proxy_fcgi 模組)

以下是一個相當簡單的範例,切勿在實際環境使用。打開 httpd.conf 設定檔,根據以下內容進行修改,修改完成後重新啟動服務。

  • Apache 安裝路徑:C:/Web/Apache24
  • PHP 安裝路徑:C:/Web/php
  • 網頁放置路徑:C:/Web/html
                
                    ServerRoot "C:/Web/Apache24"
                    DocumentRoot "C:/Web/html/"
                    <Directory "C:/Web/html/">
                        Options None
                        AllowOverride None
                        Require all granted
                    </Directory>

                    LoadModule fcgid_module modules/mod_fcgid.so
                    <IfModule fcgid_module>
                        FcgidInitialEnv PHPRC "C:/Web/php"
                        <Files ~ "\.php$">
                            Options Indexes FollowSymLinks ExecCGI
                            AddHandler fcgid-script .php
                            FcgidWrapper "C:/Web/php/php-cgi.exe" .php
                        </Files>
                    </IfModule>
                
            

執行方式 5:PHP-FPM

PHP-FPM 是 FastCGI 的行程管理器,可以自行管理行程的數量,速度更快也更穩定,所以嚴格來說 PHP-FPM 並不等於 FastCGI,這兩者很常混為一談。此外,這兩個執行方式都是多行程 (Process),每個行程單一執行緒 (Thread)。

PHP-FPM 是現在 PHP 最常使用的執行模式,效能提升非常顯著,在 Linux 及 macOS 作業系統是首選,但卻不支援 Windows 作業系統,這似乎牽涉到 PHP-FPM 核心設計問題,未來也不一定會支援。

使用方式

由於 Windows 不支援 PHP-FPM,以下是在 macOS 使用 Nginx 網頁伺服器的設定。 必須先啟動 PHP-FPM 服務,接下來打開 nginx.conf 設定檔,根據以下內容進行修改,修改完成後重新啟動服務。

  • PHP-FPM 位址:127.0.0.1:9000
  • 網頁放置路徑:/var/www/html
                
                    upstream php_fpm {
                        server 127.0.0.1:9000;
                    }

                    server {
                        listen 80;
                        server_name localhost;
                        index index.php index.htm index.html;
                        root /var/www/html;
                        charset utf-8;

                        location / {
                            try_files $uri $uri/ /index.php?$query_string;
                        }

                        location ~ \.php$ {
                            include fastcgi_params;
                            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                            fastcgi_param SERVER_SOFTWARE nginx;
                            fastcgi_index index.php;
                            fastcgi_pass php_fpm;
                            fastcgi_connect_timeout 600;
                            fastcgi_read_timeout 600;
                            fastcgi_send_timeout 600;
                        }
                    }
                
            

Non Thread Safe 與 Thread Safe

在 Windows 架設 PHP 執行環境時,細心的朋友,應該會發現在下載 PHP 的時候,有分執行緒安全 (Thread Safe) 與非執行緒安全 (Non Thread Safe) 兩個版本。根據上述的說明,我們知道當 PHP 運行在多執行緒環境時,才需要使用執行緒安全 (Thread Safe) 版本。

下表為 PHP SAPI 的總整理。

SAPI 行程 每個行程執行緒數量 獨立主機架設 建議
CLI 單行程 單執行緒 不適用
CGI 多行程 單執行緒
mod_php
prefork 單主行程
多子行程
單執行緒
worker 單主行程
多子行程
多執行緒
event 單主行程
多子行程
多執行緒
winnt 單主行程
單子行程
多執行緒
FastCGI 多行程 單執行緒 Windows 首選
PHP-FPM 多行程 單執行緒 Linux 首選
macOS 首選

由上表大概就能看出,在大部分情況下,PHP 都是單一執行緒的,只有使用 mod_php 模式時,才需要下載執行緒安全 (Thread Safe) 版本,其他執行模式通通都是單一執行緒,使用非執行緒安全 (Non Thread Safe) 版本即可。