什麼是進程php
進程Process
是計算機中的程序關於某數據集合上的一次運行活動,是系統分配資源和調度的基本單位,是操做系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體。在當代面向線程設計的計算機結構中,進程是線程的容器。簡單來講,程序是指令、數據以及其組織形式的描述,而進程則是程序的實體。前端
在操做系統中,進程表示正在運行的程序,例如在終端中使用PHP命令運行PHP腳本,此時就至關於建立了一個進程,這個進程會在系統中駐存,申請屬於它本身的內存空間和系統資源,而且運行相應的程序。shell
$ php build.php
<?php //獲取當前進程的PID echo posix_getpid(); //修改所在進程的名稱 swoole_set_process_name("swoole process master"); //模擬持續運行100秒的程序 sleep(100);//持續運行100秒的目的是爲了在進程中能夠查看而不至於很快結束
運行程序編程
$ php build.php 71
查看進程vim
$ 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
對於一個進程來講,最核心的內容可分爲兩部分:一部分是它的內存,這個內存是在建立初始時從系統中分配的,進程中全部建立的變量都會存儲在內存環境中。另外一部分是上下文環境, 進程是運行在操做系統中的,對於程序而言,它的運行依賴於操做系統分配的資源、操做系統的狀態以及程序自身的狀態,這些就構成了進程的上下文環境。數組
fd
描述符例如:父進程經過fopen
打開文件後獲得一個IO句柄fd
,子進程複製父進程後一樣會獲得這個fd
。若是父進程和子進程同時對一個文件進行操做,會形成文件混亂,所以須要加互斥鎖。緩存
例如:父進程中的變量x=1
,父進程派生子進程後,子進程也會存在變量x=1
,可是修改父進程中的變量x
並不會影響子進程的變量x
的值。安全
PHP是單進程執行的,在處理高併發時主要依賴於Web服務器或PHP-FPM的多進程管理以及進程的複用,但在PHP實現多進程尤爲是後臺PHP-CLI模式下處理大量數據或運行後臺Deamon守護進程時,多進程的優點天然是最好的。性能優化
PHP的多線程也曾被人說起,但進程內多線程資源共享和分配問題難以解決,PHP有一個多線程過的擴展pthreads
,它要求PHP環境必須是線程安全的。bash
多進程簡單來講就是多個進程同時執行多個任務,能夠將耗時但又必須執行的查詢分紅多個子進程進行操做。
開發使用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
。
對於傳統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代碼執行完畢內存就會被徹底釋放掉。
LNMP
架構中PHP
是須要依賴Nginx
這樣的Web
服務器以及PHP-FPM
這樣的多進程的PHP
解析器。當一個請求到來時PHP-FPM
會去建立一個新的進程去處理這個請求,在這種狀況下,系統的開銷很大程序上都用在建立和銷燬進程上,致使了程序的響應效率並非很是高。
Swoole的強大之處在於進程模型的設計,即解決了異步問題,又解決了併發問題。
Swoole的進程可分爲四種角色
在Swoole
中採用了和PHP-FPM
徹底不一樣的架構,整個Swoole
擴展能夠分爲三層:
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
服務器正常結束時發生Swoole啓動後Master主線程會負責監聽服務器的socket
,若是有新的鏈接accept
,Master主線程會評估每一個Reactor線程的鏈接數量,並將此鏈接分配給鏈接最少的Reactor線程。這樣作的好處是:
listen socket
時,節約了線程喚醒和切換的開銷。signal
的處理,使Reactor線程運行中能夠不被信號打斷。主線程Master在accept
新的鏈接後,會將這個鏈接分配給一個固定的Reactor線程,並由這個線程負責監聽此socket
,在socket
可讀時讀取數據,並進行協議解析,最後將請求投遞到Worker進程。
TCP
鏈接、處理網絡IO、處理協議、收發數據。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配置了心跳檢測後心跳包線程會在固定事件內對全部以前在線的鏈接發送檢測數據包。
UdpRecv
接收並處理客戶端UDP數據包
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進程的工做職責
fork
建立並管理的Manager進程內的回調函數
onManagerStart
當管理進程啓動時調用onManagerStop
當管理進程結束時調用onWorkerError
當Worker進程或Task進程發生異常後會在Manager進程會回調此函數與傳統的半同步半異步服務器不一樣是,Swoole的工做進程能夠同步的也能夠異步的。這樣帶來了工做進程相似於PHP-FPM進程,它接收由Reactor線程投遞的請求數據包,並執行PHP回調函數處理數據。工做線程生成響應數據併發送給Reactor線程,由Reactor線程發送給TCP客戶端。工做線程能夠是異步模式,也能夠是同步模式。另外,工做線程以多進程的方式運行。
Swoole想要實現最好的性能就必須建立出多個工做進程幫助處理任務,可是工做進程必須fork
操做,而fork
操做又是不安全的。若是沒有管理將會出現不少殭屍進程,進而影響服務器性能。同時工做進程被誤殺或因爲程序緣由會引發異常退出,爲了保證服務的穩定性,須要從新建立工做進程。
工做進程可分爲兩類:Worker進程和Task進程
Worker進程是Swoole的主邏輯進程,用於處理來自客戶端的請求。
Task進程是Swoole提供的異步工做進程,用於處理耗時較長的同步任務。
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
發送的管道消息時觸發swoole_server->task
或swoole_server->taskwait
方法投遞的任務。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
發送的管道消息時觸發onConnect
事件對應的方法。onReceive
事件。Send
方法將數據發回給客戶端時,數據會沿着這個路徑逆流而上。形象來講
當在業務窗口辦理業務時,若是用戶不少,後邊的用戶須要排隊等待服務,Reactor負責與客戶直接溝通,對客戶的請求進行初步的整理(傳輸層級別的整理,組包),而後Manager負責將業務分配給合適的Worker,如空閒的Worker,最終Worker負責實現具體的業務。
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是一個工廠
底層會爲Worker工做進程、Task任務進程分配一個惟一的ID,不一樣的Worker和Task任務進程之間能夠經過sendMessage
接口進行通訊。
onConnect
,也就是接收到鏈接的回調。