SWOOLE PROCESS多进程模型

<?php
class SwooleProcessController {
	
	//主进程pid
	protected $pidfile = "";
	
	//构造函数
	public function __construct(){
		$this->pidfile = LOG_PATH."swoole/consumer_master.pid";
	}
	
	//初始化一些参数
	public function start(){
		//命令
		$cmd = $_SERVER['argv'][2] ?? "start";
		//woker数量
		$workernum = $_SERVER['argv'][3] ?? 1;
		//daemon
		$daemon = in_array('-d',$_SERVER['argv'],true);
		
		//基于命令
		switch($cmd){
			case "start":
				$this->startmain($daemon,$workernum);
				break;
			case "stop":
				$this->stop();
				break;
			case "status":
				$this->status();
				break;
			default:
				echo "wrong cmd";
				exit(1);
		}
	}
	
	/**
	 * 启动进程
	 * @param int $daemon 是否守护
	 * @param int $workernum worker数量
	 */
	public function startmain($daemon,$workernum){
		if($this->is_running()){
			echo "Already running.PID=".trim(file_get_contents($this->pidfile)) . PHP_EOL;
			return;
		}
		
		//守护进程
		if($daemon){
			\Swoole\Process::daemon(true,true);
		}
		
		//leader
		posix_setsid();
		
		//主进程ID
		$masterpid = posix_getpid();
		file_put_contents($this->pidfile,(string)$masterpid);
		
		//设置主进程名称
		swoole_set_process_name("php:consumer master");
		
		$running = true;
		$workers = [];
		
		//设置异步信号监听
		pcntl_async_signals(true);
		pcntl_signal(SIGTERM,function() use (&$running,&$workers){
			debugLog("Master recv SIGTERM, worker count=".count($workers), 'swoole/consumer.log');
			$running = false;
			\Swoole\Timer::clearAll();
			foreach($workers as $pid=>$_){
				debugLog("kill child pid: {$pid}",'swoole/consumer.log');
				\Swoole\Process::kill($pid,SIGTERM);
			}
		});
		pcntl_signal(SIGINT, function() use (&$running, &$workers) {
			$running = false;
			foreach ($workers as $pid => $_) {
				\Swoole\Process::kill($pid, SIGTERM);
			}
		});
		
		//\Swoole\Timer::tick(1000, function(){});
		//\Swoole\Event::wait(); 
		
		//启动子进程worker
		for($i=0;$i<$workernum;$i++){
			$this->startworker($workers,$i);
		}
		
		//处理子进程异常退出
		\Swoole\Process::signal(SIGCHLD,function() use (&$workers,&$running){
			while($ret = \Swoole\Process::wait(false)){
				$pid = $ret['pid'];
				$code = $ret['code'];
				$sig = $ret['signal'];
				unset($workers[$pid]);
				
				if($running){
					$id = $ret['pid'] % 10000;
					$this->startworker($workers,$id);
				}
			}
		});
		
		//主进程事件循环
		while($running){
			pcntl_signal_dispatch();
			sleep(1);
		}
		
		//等待子进程退出
		$waitStart = time();
		while(!empty($workers) && time() - $waitStart < 10){
			sleep(1);
		}
		
		@unlink($this->pidfile);
		debugLog("Master Stoppd.","swoole/consumer.log");
	}
	
	/**
	 * 记动一个子进程
	 */
	public function startworker(&$workers,$workerid):void{
		$process = new \Swoole\Process(function(\Swoole\Process $proc) use ($workerid){
			swoole_set_process_name("php:consumer worker#{$workerid}");
			
			//worker主进程
			$this->workerMain($workerid);
			
		},false,0,true);
		
		$pid = $process->start();
		$workers[$pid] = $process;
		debugLog("Worker started. id={$workerid}, pid={$pid}\r\n",'swoole/consumer.log');
	}
	
	/**
	 * worker 主进程
	 */
	public function workerMain($workerid){
		$running = true;
		pcntl_async_signals(true);

		$objredis = Apps::redis('cms');

		\Swoole\Process::signal(SIGTERM, function() use (&$running, $objredis) {
			$running = false;
			$objredis->close(); // 立即唤醒 brPop
		});
		\Swoole\Process::signal(SIGINT, function() use (&$running, $objredis) {
			$running = false;
			$objredis->close();
		});

		while($running){
			try {
				$result = $objredis->brPop('testswoolekey', 5);
				if(empty($result)){
					continue;
				}
				debugLog($result[1], 'swoole/data.log');
			} catch (\Exception $e) {
				if (!$running) break;
				usleep(20000);
			}
		}

		debugLog("Worker {$workerid} exited", 'swoole/consumer.log');
	}
	
	
	//停止
	public function stop(){
		if(!$this->is_running()){
			echo "Not running";
			return ;
		}
		
		$pid = (int)trim(file_get_contents($this->pidfile));
		$pgid = posix_getpgid($pid);
		if($pgid > 0){
			echo "Stop signal sent to PGID={$pgid}\r\n";
			posix_kill(-$pgid,SIGTERM);
		} else {
			posix_kill($pid,SIGTERM);	
			echo "Stop signal sent to master PID={$pid}\r\n";
		}
		
		//等待退出
		$timeout = 10;
		while($timeout-- > 0){
			if($pgid > 0){
				if(!posix_kill($pid,0)) break;
			} else {
				if (!posix_kill($pid, 0)) break;
			}
			sleep(1);
		}
		
		
		if($pgid > 0){
			posix_kill(-$pgid, SIGKILL);
			echo "Force killing PGID={$pgid}\n";
		} else {
			\Swoole\Process::kill($pid,SIGKILL);
			echo "Force killing master PID={$pid}\r\n";	
		}
		
		@unlink($this->pidfile);
	}
	
	public function stop2()
	{
		if (!$this->is_running()) {
			echo "Not running\n";
			return;
		}

		$pid = (int)trim(@file_get_contents($this->pidfile));
		if ($pid <= 0) {
			echo "Invalid PID file\n";
			return;
		}

		$pgid = @posix_getpgid($pid);
		if ($pgid > 0) {
			echo "Sending SIGTERM to process group PGID={$pgid}\n";
			posix_kill(-$pgid, SIGTERM);
		} else {
			echo "Sending SIGTERM to master PID={$pid}\n";
			posix_kill($pid, SIGTERM);
		}

		// 等待进程优雅退出
		$timeout = 10;
		$graceful = false;
		while ($timeout-- > 0) {
			if (!posix_kill($pid, 0)) {
				$graceful = true;
				break;
			}
			echo "Waiting for process to exit... ({$timeout}s left)\n";
			sleep(1);
		}

		// 检查是否仍在运行
		if (!$graceful) {
			echo "Process did not exit in time, sending SIGKILL...\n";
			if ($pgid > 0) {
				posix_kill(-$pgid, SIGKILL);
			} else {
				posix_kill($pid, SIGKILL);
			}

			// 再次确认是否真的被杀掉
			sleep(1);
			if (posix_kill($pid, 0)) {
				echo "Warning: process still alive after SIGKILL!\n";
			} else {
				echo "Process group successfully killed.\n";
			}
		} else {
			echo "Process exited gracefully.\n";
		}

		// 最后安全删除 pid 文件
		if (file_exists($this->pidfile)) {
			@unlink($this->pidfile);
		}
	}
	//状态
	public function status(){
		if(!$this->is_running()){
			echo "not running";
			return;
		}
		$pid = (int)trim(file_get_contents($this->pidfile));
		echo "Running:Master PID={$pid}";
	}
	
	public function is_running():bool {
		if(!is_file($this->pidfile)) return false;
		$pid = (int)trim(@file_get_contents($this->pidfile));
		return $pid>0 && posix_kill($pid,0);
	}
	
}

基本实现进程启动,停止,进程数量,状态,主进程子进程信号监听

posted @ 2025-11-13 15:20  sblack  阅读(2)  评论(0)    收藏  举报