說明
- php在web編程時是不需要考慮多行程的,但整個php流程是涉及到多行程的,只不過
nginx、php-fpm幫我們處理好了,我們配置他們引數時就需要設定行程個數相關引數 - php在多行程涉及到的是PCNTL擴展和POSIX擴展,這兩個擴展交叉涉及到行程和信號相關,他們只支持Unix平臺,windows平臺不支持,而且只支持cli模式下運行,
- php也提供了其它函式來處理多行程的場景,例如
exec相關的函式,所以并不是涉及到多行程都需要上方擴展,推薦symfony/process,對php此類相關函式進行的封裝和兼容處理,同時支持linux和windows,非常好用的多行程庫,一般的中小專案完全滿足需求, - 我們只從上方擴展中找行程相關的函式來總結,信號相關的在下節中總結
概念
這里涉及到一些linux系統相關的的概念,可能需要先做了解
- 僵尸行程和孤兒行程,簡單來說就是子行程結束而父行程沒有回收善后處理則產生僵尸行程,而父行程提前結束而子行程還存在則產生孤兒行程,可參考PHP多行程初探 --- 孤兒和僵尸,
- 用戶(uid)和用戶組(gid),這個大家應該都清楚,創建linux用戶、指定nginx等執行用戶和用戶組、設定檔案權限都會用到,還有有效用戶(euid)和有效組(egid),感興趣的可以去自行搜索,
- 行程(pid,英文:process ID)、父行程(ppid)、行程組(pgid)、會話組(sid),例如我們從終端xshell登陸,就會產生一個會話組,然后我們執行命令,會在會話組下產生行程組,而行程組中就包含各種行程,可以參考從行程組、會話、終端的概念深入理解守護行程、linux內核之行程的基本概念(行程,行程組,會話關系)、PHP多行程初探 --- 再次談daemon行程
相關函式
-
PCNTL函式
pcntl_fork( void ) : int- 產生子行程分支
- 此函式后的代碼,父子行程都會執行,執行順序則是隨機的,此函式會在父行程和子行程內產生不同的回傳值,子行程回傳0,父行程則回傳子行程的pid,如果失敗則回傳-1,由于父子行程都會執行后續代碼,所以我們一般用
if來判定哪個是父行程哪個是子行程,
pcntl_wait( int &$status [, int $options = 0 ] ) : int- 用于子行程結束時,回收子行程資源
pcntl_waitpid( int $pid , int &$status [, int $options = 0 ] ) : int- 同
pcntl_wait基本一樣,但是多了指定pid的引數,當$pid=-1時,相當于pcntl_wait,其它的$pid引數自行查看檔案
- 同
- 上面兩函式第二個引數
$status是個參考,可以獲取到子行程結束時的狀態資訊,通過呼叫其它函式來決議status具體含義 - 上面兩函式第三個引數則比較費解,除了0外還有兩個選項
WNOHANG和WUNTRACED,上面兩個函式默認是阻塞的,直到有子行程結束時,才會有回應動作,如果想不阻塞,可以將$options引數設定為WNOHANG,當沒有子行程退出時不再阻塞等待,而是直接回傳0,而如果設定為WUNTRACED引數則和設定為0一樣還是阻塞,但是除了子行程結束會回應外SIGTTIN,SIGTTOU,SIGTSTP,SIGSTOP這些信號也會回應,
-
POSIX函式
posix_getpid( void ) : int- 獲取當前行程ID,常用于區分是否為父行程
posix_getppid( void ) : int- 獲取父行程ID
posix_getuid( void ) : int- 獲取當前行程的用戶ID
posix_setuid( int $uid ) : bool- 設定當前行程的用戶ID
posix_getgid( void ) : int- 獲取當前行程所屬組ID
posix_setgid( int $gid ) : bool- 設定當前行程組ID
posix_getsid( int $pid ) : int- 獲取某行程的會話ID,需要注意的是引數需要傳遞,可以先通過
posix_getpid獲取pid引數,再獲取sid
- 獲取某行程的會話ID,需要注意的是引數需要傳遞,可以先通過
- posix_setsid( void ) : int
- 設定當前行程為會話的領頭行程,需要注意引數,只能設定當前的行程
posix_getpwuid( int $uid ) : array- 根據用戶ID獲取此用戶的資訊,例如用戶的名稱、密碼,組等資訊
posix_getpwnam( string $username ) : array- 根據用戶名獲取此用戶的資訊,例如用戶的ID、密碼、組等資訊
posix_getgrgid( int $gid ) : array- 根據組ID獲取此組的資訊,例如組名稱、密碼、成員等資訊
posix_getgrnam( string $name ) : array- 根據組名稱獲取此組的資訊,例如組ID、密碼、成員等資訊
posix_initgroups( string $name , int $base_group_id ) : bool- 初始化組清單,將
$name所屬的附加組也加入到此行程的組權限中, - 這個函式不太好理解,因為和
setgid有些類似,用戶是有附加組的,同一個用戶可能屬于多個組,setuid只是設定用戶ID,setgid是設定組ID,但用戶ID可能同時還有其它組的權限,要想獲取其它組的權限,則需要運行此命令,
- 初始化組清單,將
相關代碼
- 創建子行程
$pid = pcntl_fork(); //父行程和子行程都會執行下面代碼 if ($pid == -1) { //錯誤處理:創建子行程失敗時回傳-1. die('could not fork'); } else if ($pid) { //父行程會得到子行程號,所以這里是父行程執行的邏輯 pcntl_wait($status); //等待子行程中斷,防止子行程成為僵尸行程, } else { //子行程得到的$pid為0, 所以這里是子行程執行的邏輯, } - 設定用戶ID和組
$user = 'www'; $group = 'www'; // Get uid. $user_info = posix_getpwnam($user); if (!$user_info) { echo "Warning: User {$user} not exsits"; exit; } $uid = $user_info['uid']; // Get gid. if ($group) { $group_info = posix_getgrnam($group); if (!$group_info) { echo "Warning: Group {$group} not exsits"; exit; } $gid = $group_info['gid']; } else { $gid = $user_info['gid']; } // Set uid and gid. if ($uid != posix_getuid() || $gid != posix_getgid()) { if (!posix_setgid($gid) || !posix_initgroups($user_info['name'], $gid) || !posix_setuid($uid)) { echo "Warning: change gid or uid fail."; exit; } }
需要注意
- 在fork前的變數,fork后父子都可以參考,fork后可以各自修改,但已經是獨立的會互不影響,在fork后父子各自創建的變數也不會互相影響,如果想要傳遞資訊,就需要用到行程間的通訊了,可以參考行程間通信的方式——信號、管道、訊息佇列、共享記憶體 、PHP多行程初探 --- 行程間通信二三事,
- 在回圈中的fork,第一次fork后,會產生兩個行程,而這兩個行程是在回圈中的,所以再次回圈時,這兩個行程都會創建各自的子行程,類似細胞分裂,所以回圈的個數會遠小于實際產生的子行程數,那如何避免呢,一種方法是讓子行程一直while回圈,這樣他就無法繼續執行下一次的fork(保險期間還是加上exit比較好),另一種就是子行程處理完后就exit退出,自然是不會執行下一次的fork了,
- 上方POSIX函式的主要應用場景是設定子行程以非root的權限運行,例如我們在nginx中設定的引數
user:www www,就是在指定子行程運行的用戶名和用戶組,否則子行程也用root來運行的話權限過大,存在安全隱患, - 回收子行程的幾種方式:
- 在主行程中通過
wait或waitpid來阻塞或者是回圈來處理子行程回收 - 通過信號
SIGCHLD來實作,每個子行程結束時都會發送SIGCHLD信號,父行程進行捕捉呼叫wait或waitpid來回收 - linux下還可以通過直接忽略
SIGCHLD來實作:signal(SIGCHLD, SIG_IGN),此時子行程結束時,直接由內核init行程來負責回收
上面幾種方式看似第一種不太好,因為主行程都用來處理子行程的回收了,就不能做其它的事情了,但實際上主行程本來就是來管理子行程的,并沒有多少自己的邏輯,而且越少處理邏輯越安全,所以直接在回圈中處理并沒有什么問題,
- 在主行程中通過
關聯
- 上篇:php socket網路編程基礎知識(三):stream函式
- 下篇:php socket網路編程基礎知識(五):信號(未完成)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/5061.html
標籤:PHP
