進程
什么是進程
進程Process
是計算機中的程序關於某數據集合上的一次運行活動,是系統分配資源和調度的基本單位,是操作系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體。在當代面向線程設計的計算機結構中,進程是線程的容器。簡單來說,程序是指令、數據以及其組織形式的描述,而進程則是程序的實體。
在操作系統中,進程表示正在運行的程序,例如在終端中使用PHP命令運行PHP腳本,此時就相當於創建了一個進程,這個進程會在系統中駐存,申請屬於它自己的內存空間和系統資源,並且運行相應的程序。
$ php build.php
<?php //獲取當前進程的PID echo posix_getpid(); //修改所在進程的名稱 swoole_set_process_name("swoole process master"); //模擬持續運行100秒的程序 sleep(100);//持續運行100秒的目的是為了在進程中可以查看而不至於很快結束
運行程序
$ php build.php
71
查看進程
$ ps aux | grep 71
root 1 0.0 0.1 18188 1712 pts/0 Ss+ 11:07 0:00 /bin/bash
root 71 0.0 3.0 340468 30788 pts/2 S+ 13:41 0:00 swoole process master
root 76 0.0 0.0 11112 940 pts/1 S+ 13:42 0:00 grep 71
對於一個進程來說,最核心的內容可分為兩部分:一部分是它的內存,這個內存是在創建初始時從系統中分配的,進程中所有創建的變量都會存儲在內存環境中。另一部分是上下文環境, 進程是運行在操作系統中的,對於程序而言,它的運行依賴於操作系統分配的資源、操作系統的狀態以及程序自身的狀態,這些就構成了進程的上下文環境。
父子進程

- 子進程會復制父進程的內存空間和上下文環境
- 子進程會復制父進程的IO句柄即
fd
描述符 - 子進程的內存空間與父進程的內存空間是獨立,是互不影響的。
- 修改子進程的內存空間並不會修改父進程或其他子進程的內存空間
例如:父進程通過fopen
打開文件后得到一個IO句柄fd
,子進程復制父進程后同樣會得到這個fd
。如果父進程和子進程同時對一個文件進行操作,會造成文件混亂,因此需要加互斥鎖。
例如:父進程中的變量x=1
,父進程派生子進程后,子進程也會存在變量x=1
,但是修改父進程中的變量x
並不會影響子進程的變量x
的值。
多進程
PHP是單進程執行的,在處理高並發時主要依賴於Web服務器或PHP-FPM的多進程管理以及進程的復用,但在PHP實現多進程尤其是后台PHP-CLI模式下處理大量數據或運行后台Deamon守護進程時,多進程的優勢自然是最好的。
PHP的多線程也曾被人提及,但進程內多線程資源共享和分配問題難以解決,PHP有一個多線程過的擴展pthreads
,它要求PHP環境必須是線程安全的。
多進程簡單來說就是多個進程同時執行多個任務,可以將耗時但又必須執行的查詢分成多個子進程進行操作。
- PHP多進程不支持PHP-FPM和CGI模式,只能通過PHP-CLI模式。
- PHP多進程適用於定時任務執行,互斥且耗時的任務。
開發使用PHP多進程的場景也就是使用PHP-FPM,PHP-FPM作為PHP的多進程管理器,當使用Nginx作為WebServer時,來自客戶端的請求會根據Nginx的路由配置,將以PHP為后綴的文件轉發給PHP-FPM。當多個用戶同時請求API時,PHP-FPM會開啟多個PHP的處理進程進行處理。
檢查PHP是否支持多進程擴展
$ php -m | grep pcntl
多進程的優勢
PHP相比C、C++、Java少了多線程,PHP中只有多進程的方案,所以PHP中的全局變量和對象不是共享的,數據結構也不能跨進程操作,另外Socket文件描述符也不能共享...
多線程看似比多進程強大的多,多線程的缺陷也同樣明顯:
- 數據同步時,要么犧牲性能到處加鎖,要么使用地獄難度的無鎖並發編程。
- 當程序邏輯復雜后,鎖會越來越難以控制。一旦死鎖,程序基本上就完了。
- 某個線程掛掉后所有的線程都會退出
相比較多線程,多進程擁有的優勢是
- 配合進程間通信,基本可以實現任意數據共享。
- 多進程不需要鎖
- 多進程可以共享內存的數據結構實現一些多線程的功能
對於並發服務器核心是IO,並非大規模密集運算,高並發的服務器單機能維持10W連接,每秒可以處理3~5W筆消息收發。
普通的Web應用都是IO密集型的程序,瓶頸在MySQL上,所以體現不出PHP的性能優勢。但在密集計算方面比C/C++、Java等靜態編譯語言相差幾十倍甚至上百倍。
例如:使用多進程方式同時訪問Web地址
$ vim multi.php
<?php echo "process begin: ".date("Y-m-d H:i:s").PHP_EOL; //初始化地址數組 $urls = [ "http://www.baidu.com", "http://www.360.com", "http://www.qq.com", "http://www.sina.com" ]; //初始化數組用於回收線程管道內容 $workers = []; //按照任務分配線程 for($i=0; $i<count($urls); $i++){ $url = $urls[$i]; //創建進程 $process = new swoole_process(function(swoole_process $worker) use($url){ //模擬執行耗時任務 file_get_contents($url); //sleep(1);//模擬耗時1秒 echo $url.PHP_EOL; }, true); //開啟進程 $pid = $process->start(); $workers[$pid] = $process; } //打印管道內容 foreach($workers as $worker){ echo "pid : ".$worker->read(); } echo "process end: ".date("Y-m-d H:i:s").PHP_EOL;
運行代碼
$ php multi.php
process begin: 2019-06-22 16:19:48
pid : http://www.baidu.com
pid : http://www.360.com
pid : http://www.qq.com
pid : http://www.sina.com
process end: 2019-06-22 16:19:49
內存共享
進程之間是相互獨立的,那么如何實現進程之間的通信呢? 這里可以使用共享內存的方式來實現。
共享內存ShareMemory
是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的IPC
方式,是針對其他進程之間通信效率低下而專門設計的,它往往與其它通信機制,如信號量配置使用以實現進程之間的同步和通信。
共享內存是操作系統中比較特殊的內存,它並不依賴於任何進程, 也不屬於任何進程。通過調用系統函數創建共享內存,並指定它的索引,也就是它的IDshmid
,通過索引任何進程都可以在共享內存中申請內存空間並存儲對應的值。

- 共享內存並不屬於任何一個進程
- 在共享內存中分配的內存空間可以被任何進程訪問
- 即使進程關閉,共享內存仍然可以繼續保存在操作系統中。
查看操作系統中共享內存的分片
$ ipcs -m
------------ 共享內存段 --------------
鍵 shmid 擁有者 權限 字節 連接數 狀態
0x00000000 131072 jc 777 16384 1 目標
0x00000000 327681 jc 600 67108864 2 目標
0x00000000 262146 jc 777 8077312 2 目標
Swoole沒有采用多線程模型而使用了多線程模型,在一定程度上減少了訪問數據時加鎖解鎖的開銷,但同時也引入了新的需求 共享內存。Swoole中為了更好的進行內存管理,減少頻繁分配釋放內存空間造成的損耗和內存碎片,Rango實際並實現了三種不同功能的內存池分別時FixedPool
、RingBuffer
、MemoryGlobal
。
Swoole開發模式
對於傳統PHP的Web開發而言,最常用的是LNMP架構。在LNMP架構中,當請求進入時,WebServer會將請求轉交給PHP-FPM,PHP-FPM是一個進程池架構的FastCGI服務,內置了PHP解釋器。PHP-FPM負責解釋執行PHP文件並生成響應,最終返回給WebServer展現至前端。由於PHP-FPM本身是同步阻塞進程模型,在請求結束后會釋放掉所有資源,包括框架初始化創建的一些列對象,從而導致PHP進程進入“空轉”消耗大量CPU資源,最終導致單機的吞吐能力有限。
另外,在每次請求處理的過程都意味着一次PHP文件解析、環境設置等不必要的耗時操作,當PHP進程處理完后就會銷毀,無法在PHP程序中使用連接池等技術實現性能優化。
針對傳統架構的問題,Swoole從PHP擴展下手,解決了上述問題。相比較傳統的Web架構,Swoole進程模型最大的特點在於多線程Reactor模式處理網絡請求,使其能輕松應對大量連接。
除此之外,Swoole是全異步非阻塞,因此占用資源少,程序執行效率高。在Swoole中程序運行只解析加載一次PHP文件,避免每次請求的重復加載。再者,Swoole進程常駐,使得連接池和請求之間的信息傳遞的實現成為可能。
使用Swoole開發時,需要開發人員對多進程的運行模式有着清晰的認識。另外,Swoole很容易造成內存泄露。在處理全局變量、靜態變量的時候要小心,這種不會被GC清理的變量會存在整個生命周期中。如果沒有正確的處理,很容易消耗完內存。而在PHP-FPM下,PHP代碼執行完畢內存就會被完全釋放掉。
Swoole進程結構
LNMP
架構中PHP
是需要依賴Nginx
這樣的Web
服務器以及PHP-FPM
這樣的多進程的PHP
解析器。當一個請求到來時PHP-FPM
會去創建一個新的進程去處理這個請求,在這種情況下,系統的開銷很大程序上都用在創建和銷毀進程上,導致了程序的響應效率並不是非常高。
Swoole的強大之處在於進程模型的設計,即解決了異步問題,又解決了並發問題。
Swoole的進程可分為四種角色
- Master進程
保證Swoole機制運行,同時利用它創建Master主線程(負責接收連接、定時器等)和Reactor線程(處理連接並將請求分發給各個Worker進程)。 - Manager進程
Worker進程和Task進程均由Manager進程派生,Manager管理進程負責結束時回收子進程,避免僵屍進程的存在。 - Worker進程
用PHP回調函數處理由Reactor分發過來的請求數據,並生成響應數據發送給Reactor,由Reactor發送給TCP客戶端。 - Task進程
接收由Worker進程分發給它的任務,以多進程方式運行,處理好后將結果返回給它的Worker進程。

在Swoole
中采用了和PHP-FPM
完全不同的架構,整個Swoole
擴展可以分為三層:

第1層:Master主進程

Master進程是Swoole的主進程,主要用於處理Swoole的核心事件驅動。Master主進程是一個多線程模型,擁有多個獨立的Reactor線程。
Master主進程包含Master線程、Reactor線程、心跳檢測線程、UDP收包線程。每個Reactor子線程中都運行着一個epoll
函數的實例,Swoole對於事件的監聽都會在Reactor線程中實現,比如來自客戶端的連接、本地通信使用的管道、異步操作使用的文件以及文件描述符都會注冊在epoll
函數中。
Master主進程使用select/poll
進行IO
事件循環,Master主進程中的文件描述符只有幾個,Reactor線程使用epoll
,因為Reactor線程中會監聽大量連接的可讀事件,使用epoll
可以支持大量的文件描述符。
以HTTP
服務器為例,Master
主進程負責監聽端口,然后接收新的連接,並將這個連接分配給一個Reactor
線程,由這個Reactor
線程監聽此連接,一旦此連接可讀時,它會讀取數據並解析協議,然后將請求投遞到Worker
工作進程中去執行。
Master主進程內的回調函數
onStart
服務器啟動時主進程的主線程回調此函數onShutdown
服務器正常結束時發生
Master 線程
Swoole啟動后Master主線程會負責監聽服務器的socket
,如果有新的連接accept
,Master主線程會評估每個Reactor線程的連接數量,並將此連接分配給連接最少的Reactor線程。這樣做的好處是:
- 每個Reactor線程持有的連接數非常均衡,沒有單個線程負載過高的問題。
- 解決了驚群問題,尤其是擁有多個
listen socket
時,節約了線程喚醒和切換的開銷。 - 主線程接管了所有信號
signal
的處理,使Reactor線程運行中可以不被信號打斷。
主線程Master在accept
新的連接后,會將這個連接分配給一個固定的Reactor線程,並由這個線程負責監聽此socket
,在socket
可讀時讀取數據,並進行協議解析,最后將請求投遞到Worker進程。

Reactor線程
- 負責維護客戶端
TCP
連接、處理網絡IO、處理協議、收發數據。 - 完全是異步非阻塞的模式
- 全部都是C代碼,除了
Start/Shutdown
事件回調外,不執行任何PHP
代碼。 - 將
TCP
客戶端發送來的數據緩沖、拼接、拆分為完整的請求數據包。 Reactor
以多線程的方式運行
Swoole擁有多線程Reactor,所以可以充分利用多核,開啟CPU親和設置后,Reactor線程可以綁定單獨的核,節省CPU Cache開銷。
Reactor線程負責處理TCP
連接,是收發數據的線程。Swoole的Master主線程在accept
新的連接后,會將這個連接分配給一個固定的Reactor線程,並由這個線程負責監聽此socket
。在socket
可讀時讀取數據,並進行協議解析,將請求投遞到Worker工作進程。在socket
可寫時,將數據發送給TCP
客戶端。
Reactor線程負責維護客戶端TCP連接、處理網絡IO、處理協議、收發數據,它完全是異步非阻塞的模式。
Reactor線程是全異步非阻塞的,即使Worker進程采用了同步模式,依然不響應Reactor線程的性能。在Worker進程組很繁忙的狀態下,Reactor線程完全不受影響,依然可以收發處理數據。
由於TCP是流式的沒有邊界,所以處理起來很麻煩。Reactor線程可以使用EOF或者包頭長度,自動緩存數據、組裝數據包,等一個請求完全收到后,再次遞交給Worker。
Reactor全部是C代碼,除了Start/Shutdown事件回調外,不執行任何PHP代碼。它將TCP客戶端發來的數據緩沖、拼接、拆分成完整的一個請求數據包。
綜上所述,Master主進程中包含兩個關鍵線程:Master主線程和Reactor線程,Master主線程用來處理accept()
事件,創建新的socket fd
,當它接收到新連接后會將新的socket
連接放到Reactor線程的事件監聽循環中,Reactor線程負責接收從客戶端發送過來的數據,並按協議解析后通過管道pipe
傳遞給Worker工作進程進行處理。Worker工作進程處理完畢后,會將結果通過管道pipe
回傳給Reactor線程,Reactor線程再按照協議將結果通過socket
發送給客戶端。可以看到Reactor線程負責數據的IO和傳輸,在Linux系統下這些IO事件都是通過epoll
機制來處理的。
心跳包檢測線程HeartbeatCheck
Swoole配置了心跳檢測后心跳包線程會在固定事件內對所有之前在線的連接發送檢測數據包。
UDP收包線程UdpRecv
接收並處理客戶端UDP數據包
第2層:Manager管理進程
Swoole運行中會創建一個單獨的管理進程,所有的Worker進程和Task進程都是從管理進程fork
創建出來的。
Manager管理進程會監聽所有子進程的退出事件,當Worker進程發生致命錯誤或運行生命周期結束時,Manager管理進程會回收此進程並創建新的進程。
Manager管理進程還可以平滑地重啟所有工作進程Worker,以實現程序代碼的重新加載。
Manager管理進程管理着Worker工作進程或Task任務進程,Worker工作進程或Task任務進程都被Manager管理進程fork
創建並管理着。
Manager進程負責創建和管理下層的Worker進程組和Task進程組,Manager進程中不會運行任何用戶層面的業務邏輯,僅僅只做進程的管理和分配。
Manager進程會fork
創建出指定數量的Worker進程和Task進程。
Manager進程的工作職責
- Worker工作進程和Task任務進程都是由Manager管理進程
fork
創建並管理的 - 子進程結束運行時,Manager管理進程負責回收子進程,以避免成為僵屍進程,並創建新的子進程。
- 服務器關閉時,Manager管理進程發送信號給所有子進程,並通知子進程關閉服務。
- 服務器重啟時,Manager管理進程會逐個關閉或重啟子進程。
Manager進程內的回調函數
onManagerStart
當管理進程啟動時調用onManagerStop
當管理進程結束時調用onWorkerError
當Worker進程或Task進程發生異常后會在Manager進程會回調此函數
第3層:工作進程
- 工作進程主要用於處理客戶端請求
- 工作進程接收由Reactor線程投遞的請求數據包,並執行PHP回調函數處理數據。
- 工作進程生成響應數據並發送給Reactor線程,由Reactor線程發送給TCP客戶端。
- 工作進程可以是異步非阻塞模式也可以是同步阻塞模式。
- 工作進程以多進程的方式運行
與傳統的半同步半異步服務器不同是,Swoole的工作進程可以同步的也可以異步的。這樣帶來了工作進程類似於PHP-FPM進程,它接收由Reactor線程投遞的請求數據包,並執行PHP回調函數處理數據。工作線程生成響應數據並發送給Reactor線程,由Reactor線程發送給TCP客戶端。工作線程可以是異步模式,也可以是同步模式。另外,工作線程以多進程的方式運行。
Swoole想要實現最好的性能就必須創建出多個工作進程幫助處理任務,但是工作進程必須fork
操作,而fork
操作又是不安全的。如果沒有管理將會出現很多僵屍進程,進而影響服務器性能。同時工作進程被誤殺或由於程序原因會引起異常退出,為了保證服務的穩定性,需要重新創建工作進程。
工作進程可分為兩類:Worker進程和Task進程
-
Worker進程是Swoole的主邏輯進程,用於處理來自客戶端的請求。
-
Task進程是Swoole提供的異步工作進程,用於處理耗時較長的同步任務。
Worker工作進程
Worker工作進程接收Reactor線程投遞過來的數據,執行PHP代碼,然后生成數據並交給Reactor線程,由Reactor線程通過TCP
將數據返回給客戶端。如果是UDP
,Worker工作進程會直接將數據發送給客戶端。
Worker進程中執行的PHP
代碼,它等同於PHP-FPM
。PHP-FPM
在處理異步操作時是很無力的,但Swoole提供的Task進程可以很好的解決這個問題。Worker進程可以將一些異步任務投遞給Task進程,然后直接返回,處理其他由Reactor線程投遞過來的事件。
Worker進程內的回調函數
onWorkerStart
當Worker工作進程或Task任務進程啟動時觸發onWorkerStop
當Worker進程終止時觸發onConnect
當有新的連接進入時觸發onClose
當TCP客戶端連接關閉后觸發onReceive
當接收到數據時觸發onPacket
當接收到UDP數據包是時觸發onFinish
當Worker工作進程投遞的任務在task_worker
中完成時,Task進程會通過finish()
方法將任務處理的結果發送給Worker進程。onWorkerExit
當開啟reload_async
特性后有效,即異步重啟特性。onPipeMessage
當工作進程收到由sendMessage
發送的管道消息時觸發
Task任務進程
- 異步工作進程
- 接收由Worker工作進程通過
swoole_server->task
或swoole_server->taskwait
方法投遞的任務。 - 處理任務,並將結果數據返回給Worker工作進程
swoole_server->finish
。 - 同步阻塞模式
- 以多進程的方式運行
Swoolen除了Reactor線程,Task任務工作進程是以異步的方式處理其它任務的進程,使用方式類似於Gearman。它接收由Worker進程通過swoole_server->task/taskwait
方法投遞的任務,然后處理任務,並將結果數據使用swoole_server->finish
返回給Worker進程。Task以多進程的方式進行運行。
簡單來說,可以將Reactor理解為Nginx,將Worker理解為PHP-FPM。Reactor線程異步並行地處理網絡請求,然后再轉發給Worker工作進程中去處理(在回調函數中處理)。Reactor和Worker之間通過UnixSocket進行通信。
Swoole除了Reactor線程,Worker工作進程還提供了Task進程池。目的是為了解決業務代碼中,有些邏輯部分不需要馬上執行。利用Task進程池,可以方便的投遞一個異步任務區執行。
Task進程以完全同步阻塞的方式運行,一個Task進程在執行任務期間是不接受從Worker進程投遞的任務的,當Task進程執行完任務后,會異步的通知Worker進程並告訴它任務已經完成。
Task進程內的回調函數
onTask
在Task線程內被調用,Worker進程可使用swoole_server_task
函數向Task進程投遞新的任務。onWorkerStart
在Worker或Task進程啟動時觸發onPipeMessage
當工作進程收到由sendMessage
發送的管道消息時觸發

Swoole進程協作
- 當客戶端主動連入服務器的時候,客戶端實際上是與Master主進程中的某個Reactor線程發生了連接。
- 當TCP三次握手成功后,由這個Reactor線程將連接成功的消息告知Manager管理進程,再由Manager管理進程轉交給Worker工作進程,最終在Worker工作進程中觸發
onConnect
事件對應的方法。 - 當客戶端向服務器發送一個數據包的時候,首先接收到數據包的是Reactor線程,同時Reactor線程會完成組包,再將組裝好的包交給Manager管理進程,由Manager管理進程轉交給Worker工作進程,此時Worker工作進程觸發
onReceive
事件。 - 如果Worker工作進程中做了處理操作后再使用
Send
方法將數據發回給客戶端時,數據會沿着這個路徑逆流而上。 - Task任務進程用來處理一些占用時間較長的業務,主要處理Worker工作進程中占用時間較長的任務。
形象來說
- Master主進程 = 業務窗口
- Reactor線程 = 前台接待員
- Manager管理進程 = 項目經理
- Worker工作進程 = 工人
當在業務窗口辦理業務時,如果用戶很多,后邊的用戶需要排隊等待服務,Reactor負責與客戶直接溝通,對客戶的請求進行初步的整理(傳輸層級別的整理,組包),然后Manager負責將業務分配給合適的Worker,如空閑的Worker,最終Worker負責實現具體的業務。
Swoole進程關系
Reactor和Worker與Task的關系,簡單理解可認為:Reactor = Nginx、Worker = PHP-FPM
Reactor線程異步並行地處理網絡請求,然后再轉發給Worker工作進程中去處理。
Reactor線程和Worker工作進程之間通過socket
進行通信。
在PHP-FPM的應用中,經常會將一個任務異步投遞到Redis等隊列中,並在后台啟動一些PHP進程異步地處理這些任務。
Swoole提供的Worker工作進程是一套更加完整的方案,它將任務投遞、隊列、PHP任務進程管理融為一體。通過底層的API實現異步任務的處理。另外,Task任務進程可以在任務執行完畢后,再返回一個結果反饋到Worker工作進程。
Swoole的Reactor、Worker、Task之間可以緊密的結合起來,提供更加高級的使用方式。
假設Server是一個工廠
- Reactor:銷售,接收客戶訂單。
- Worker:工人,當銷售接單后,Worker去工作生產出客戶需要的東西。
- Task:行政人員,幫助Worker干些雜事兒,讓Worker專心工作。
底層會為Worker工作進程、Task任務進程分配一個唯一的ID,不同的Worker和Task任務進程之間可以通過sendMessage
接口進行通信。
Swoole執行流程

- 當客戶端請求進入Master主進程后會被Master主線程接收到
- 將讀寫操作的監聽注冊到對應的Reactor線程中,並通知Worker工作進程處理
onConnect
,也就是接收到連接的回調。 - 客戶端的數據會通知對應的Reactor線程並發送給Worker工作進程進行處理。
- 如果Worker工作進程投遞任務,將數據通過管道發送給Task任務進程,Task任務進程處理完后會發送給Worker工作進程。
- Worker工作進程會通知Reactor線程發送數據給客戶端。
- 當Worker工作進程出現異常時關閉,Manager管理進程會重新創建一個Worker工作進程,保證Worker工作進程的數量是固定的。
