本文章基於pcntl擴充套件做的多程式測試。
程式排程策略
父子程式的排程由作業系統來負責,具體先排程子程式還是父程式由系統的排程演算法決定,當然可以在父程式加上延時或是呼叫程式回收函式pcntl_wait可以先讓子程式先執行,程式回收的目的是釋放程式建立時佔用的記憶體空間,防止變成殭屍程式。
訊號:
訊號稱為軟中斷系統或是叫軟中斷,功能是向程式傳送非同步事件通知。
訊號編號: 【原始碼基於SIGINT,SIGTERM,SIGUSR1訊號,含義請自行檢視kill命令手冊,不在描述】
linux支援64個,有一半為實時訊號,一半為非時實訊號,這些訊號都有自己的編號和對應的整數值。每個訊號的編號含義讀者可以參閱linux相關手冊【man手冊看看就知道了】
訊號處理函式:
訊號一般會繫結相應的功能,有的是預設動作如SIGKILL,SIGTERM,SIGINT操作預設操作就是幹掉程式,當然我們可以重寫覆蓋掉,就是通過pcntl_signal來覆蓋掉。
訊號的概念:與硬體中斷一個道理,請讀者自行參考本人前面擼過的文章或是檢視晶片硬體中斷原理。
訊號的傳送:
kill 訊號編號 程式 或是按鍵產品的中斷訊號或是在原始碼裡可以使用posix_kill等函式。
程式是相互隔離的,擁有自己的堆疊空間,除了一些公用的正文【程式碼區】,同時也有自己的可執行程式碼,程式執行時,將佔用cpu的資源,其它程式將無權執行,此時其它程式將為阻塞狀態【比如前面擼過的tcp服務】,當程式執行結束後【執行到程式碼的最後一句或是遇到return或是遇到exit退出程式函式或是遇到訊號事件時將會退出】讓出許可權並釋放掉記憶體,其它程式就有機會執行了。
程式擁有的自己程式描述符,其中比較常用的是程式號PID,程式執行時會在系統/proc/PID下生成相應的程式檔案,使用者可以自行檢視。
每個程式都擁有所屬的程式組【程式的集合】,多個程式組集合則是一個會話,建立一個會話是通過一個程式進行建立的,並且此程式不可以為組長程式,此程式將成為會話期的會話首程式,也會成為程式組的程式組長,同時將會脫離控制終端,即使之前的程式繫結了控制終端也會脫離【守護程式的建立】。
檔案描述許可權掩碼【許可權遮蔽字】:
umask() 你可以在linux執行這個命令,然後建立檔案,並檢視它的許可權【如果你跑完啥也沒有發現,說明你還是訓練不夠^_^】
<?php
/**
* Created by PhpStorm.
* User: 1655664358@qq.com
* Date: 2018/3/26
* Time: 14:19
*/
namespace Chen\Worker;
class Server
{
public $workerPids = [];
public $workerJob = [];
public $master_pid_file = "master_pid";
public $state_file = "state_file.txt";
function run()
{
$this->daemon();
$this->worker();
$this->setMasterPid();
$this->installSignal();
$this->showState();
$this->wait();
}
function wait()
{
while (1){
pcntl_signal_dispatch();
$pid = pcntl_wait($status);
if ($pid>0){
unset($this->workerPids[$pid]);
}else{
if (count($this->workerPids)==0){
exit();
}
}
usleep(100000);
}
}
function showState()
{
$state = "\nMaster 資訊\n";
$state.=str_pad("master pid",25);
$state.=str_pad("worker num",25);
$state.=str_pad("job pid list",10)."\n";
$state.=str_pad($this->getMasterPid(),25);
$state.=str_pad(count($this->workerPids),25);
$state.=str_pad(implode(",",array_keys($this->workerPids)),10);
echo $state.PHP_EOL;
}
function getMasterPid()
{
if (file_exists($this->master_pid_file)){
return file_get_contents($this->master_pid_file);
}else{
exit("服務未執行\n");
}
}
function setMasterPid()
{
$fp = fopen($this->master_pid_file,"w");
@fwrite($fp,posix_getpid());
@fclose($fp);
}
function daemon()
{
$pid = pcntl_fork();
if ($pid<0){
exit("fork程式失敗\n");
}else if ($pid >0){
exit(0);
}else{
umask(0);
$sid = posix_setsid();
if ($sid<0){
exit("建立會話失敗\n");
}
$pid = pcntl_fork();
if ($pid<0){
exit("程式建立失敗\n");
}else if ($pid >0){
exit(0);
}
//可以關閉標準輸入輸出錯誤檔案描述符【守護程式不需要】
}
}
function worker()
{
if (count($this->workerJob)==0)exit("沒有工作任務\n");
foreach($this->workerJob as $job){
$pid = pcntl_fork();
if ($pid<0){
exit("工作程式建立失敗\n");
}else if ($pid==0){
/***************子程式工作範圍**********************/
//給子程式安裝訊號處理程式
$this->workerInstallSignal();
$start_time = time();
while (1){
pcntl_signal_dispatch();
if ((time()-$start_time)>=$job->job_run_time){
break;
}
$job->run(posix_getpid());
}
exit(0);//子程式執行完成後退出
/***************子程式工作範圍**********************/
}else{
$this->workerPids[$pid] = $job;
}
}
}
function workerInstallSignal()
{
pcntl_signal(SIGUSR1,[__CLASS__,'workerHandleSignal'],false);
}
function workerHandleSignal($signal)
{
switch ($signal){
case SIGUSR1:
$state = "worker pid=".posix_getpid()."接受了父程式發來的自定義訊號\n";
file_put_contents($this->state_file,$state,FILE_APPEND);
break;
}
}
function installSignal()
{
pcntl_signal(SIGINT,[__CLASS__,'handleMasterSignal'],false);
pcntl_signal(SIGTERM,[__CLASS__,'handleMasterSignal'],false);
pcntl_signal(SIGUSR1,[__CLASS__,'handleMasterSignal'],false);
}
function handleMasterSignal($signal)
{
switch ($signal){
case SIGINT:
//主程式接受到中斷訊號ctrl+c
foreach ($this->workerPids as $pid=>$worker){
posix_kill($pid,SIGINT);//向所有的子程式發出
}
exit("服務平滑停止\n");
break;
case SIGTERM://ctrl+z
foreach ($this->workerPids as $pid=>$worker){
posix_kill($pid,SIGKILL);//向所有的子程式發出
}
exit("服務停止\n");
break;
case SIGUSR1://使用者自定義訊號
if (file_exists($this->state_file)){
unlink($this->state_file);
}
foreach ($this->workerPids as $pid=>$worker){
posix_kill($pid,SIGUSR1);
}
$state = "master pid\n".$this->getMasterPid()."\n";
while(!file_exists($this->state_file)){
sleep(1);
}
$state.= file_get_contents($this->state_file);
echo $state.PHP_EOL;
break;
}
}
}
<?php
/**\
* Created by PhpStorm.\ * User: 1655664358@qq.com
* Date: 2018/3/26\ * Time: 14:37\ */\namespace Chen\Worker;
class Job
{
public $job_run_time = 3600;
function run($pid)
{\sleep(3);
echo "worker pid = $pid job 沒事幹,就在這裡job\n";
}
}
<?php
/**
* Created by PhpStorm.\ * User: 1655664358@qq.com
* Date: 2018/3/26\ * Time: 14:37\ */\namespace Chen\Worker;
class Talk
{
public $job_run_time = 3600;
function run($pid)
{\sleep(3);
echo "worker pid = $pid job 沒事幹,就在這裡talk\n";
}
}
<?php
/**
* Created by PhpStorm.\ * User: 1655664358@qq.com
* Date: 2018/3/26\ * Time: 15:45\ */
require_once 'vendor/autoload.php';
$process = new \Chen\Worker\Server();
$process->workerJob = [new \Chen\Worker\Talk(),new \Chen\Worker\Job()];
$process->run();