guanhui07/blog

信号与进程

Opened this issue · 0 comments

信号与进程
信号量 socket 消息队列 共享内存
信号是一种用来控制进程的机制。进程是指正在执行的程序,它可以在后台运行或在前台与用户交互。

通过发送信号,您可以控制进程的执行,例如终止进程、暂停和恢复进程等。
信号是通过 PHP 的 pcntl_signal() 函数来处理的。
pcntl_signal_dispatch(
举个例子,如果您希望在进程执行到特定的位置时终止进程,您可以
使用 pcntl_signal() 函数注册一个信号处理器,当信号被触发时,
该处理器就会执行指定的动作,例如终止进程。

简而言之,信号与进程是 PHP 中的重要概念,它们可用来控制程序的执行。
通过使用 pcntl_signal() 函数,您可以创建信号
处理器来响应信号,从而控制进程的执行。

PHP信号是一种操作系统的机制,用于在进程之间进行通信。
它可以通过终止程序、触发自定义事件等方式来传递信息。

PHP进程是一个独立的执行单元,拥有自己的地址空间、数据和指令集。
它可以用来执行不同的任务,以便于多线程并发执行。

使用信号与进程可以提高PHP程序的性能,同时也可以实现复杂的逻辑。
但要注意,由于信号与进程都是操作系统级别的机制,

在 PHP 中,pcntl_signal() 函数可以用来处理进程中的信号。
信号是一种轻量级的进程间通信方式,它可以在进程之间传递一个特定的消息,
表示某种状态发生了改变。例如,当一个进程收到 SIGINT 信号(即中断信号)时,它可能会停止执行并退出。

使用 pcntl_signal() 函数,你可以设置一个处理程序来响应特定的信号。
这样,当进程收到指定的信号时,处理程序就会被执行。这个函数接受两个参数:
要处理的信号编号,以及一个用于处理该信号的 PHP 函数。

例如,下面的代码演示了如何使用 pcntl_signal() 函数来处理 SIGINT 信号:

// 设置处理程序
function handle_sigint() {
    echo "Caught SIGINT...\n";
    exit;
}

// 使用 pcntl_signal() 函数处理信号
pcntl_signal(SIGINT, 'handle_sigint');

// 执行一些操作
while (true) {
    echo "Hello, world!\n";
    sleep(1);
}

在这个例子中,当进程收到 SIGINT 信号时,handle_sigint() 函数就
会被执行,并打印一条消息,然后退出进程。

需要注意的是,使用 pcntl_signal() 函数处理信号需要 PHP 的 pcntl 扩展支持。

pcntl_signal_dispatch() 函数是用来处理信号的队列的。当 PHP
脚本收到一个信号时,它会将这个信号放到一个队列中,等待处理。
pcntl_signal_dispatch() 函数用来检查这个队列,并执行队列中的信号处理程序。

例如,假设我们已经设置了一个处理程序来响应 SIGINT 信号,
我们可以使用 pcntl_signal_dispatch() 函数来处理信号队列:

// 设置处理程序
function handle_sigint() {
    echo "Caught SIGINT...\n";
    exit;
}

// 使用 pcntl_signal() 函数处理信号
pcntl_signal(SIGINT, 'handle_sigint');

// 执行一些操作
while (true) {
    echo "Hello, world!\n";
    sleep(1);

    // 检查信号队列,并执行处理程序
    pcntl_signal_dispatch();
}

在这个例子中,我们每次循环都会调用 pcntl_signal_dispatch() 函数,
来检查信号队列,并执行处理程序。

workerman

   protected static function installSignal()
    {
        if (static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        $signalHandler = '\Workerman\Worker::signalHandler';
        // stop
        \pcntl_signal(\SIGINT, $signalHandler, false);
        // graceful stop
        \pcntl_signal(\SIGTERM, $signalHandler, false);
        // reload
        \pcntl_signal(\SIGUSR1, $signalHandler, false);
        // graceful reload
        \pcntl_signal(\SIGQUIT, $signalHandler, false);
        // status
        \pcntl_signal(\SIGUSR2, $signalHandler, false);
        // connection status
        \pcntl_signal(\SIGIO, $signalHandler, false);
        // ignore
        \pcntl_signal(\SIGPIPE, \SIG_IGN, false);
    }
     protected static function reinstallSignal()
    {
        if (static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        $signalHandler = '\Workerman\Worker::signalHandler';
        // uninstall stop signal handler
        \pcntl_signal(\SIGINT, \SIG_IGN, false);
        // uninstall graceful stop signal handler
        \pcntl_signal(\SIGTERM, \SIG_IGN, false);
        // uninstall reload signal handler
        \pcntl_signal(\SIGUSR1, \SIG_IGN, false);
        // uninstall graceful reload signal handler
        \pcntl_signal(\SIGQUIT, \SIG_IGN, false);
        // uninstall status signal handler
        \pcntl_signal(\SIGUSR2, \SIG_IGN, false);
        // uninstall connections status signal handler
        \pcntl_signal(\SIGIO, \SIG_IGN, false);
        // reinstall stop signal handler
        static::$globalEvent->add(\SIGINT, EventInterface::EV_SIGNAL, $signalHandler);
        // reinstall graceful stop signal handler
        static::$globalEvent->add(\SIGTERM, EventInterface::EV_SIGNAL, $signalHandler);
        // reinstall reload signal handler
        static::$globalEvent->add(\SIGUSR1, EventInterface::EV_SIGNAL, $signalHandler);
        // reinstall graceful reload signal handler
        static::$globalEvent->add(\SIGQUIT, EventInterface::EV_SIGNAL, $signalHandler);
        // reinstall status signal handler
        static::$globalEvent->add(\SIGUSR2, EventInterface::EV_SIGNAL, $signalHandler);
        // reinstall connection status signal handler
        static::$globalEvent->add(\SIGIO, EventInterface::EV_SIGNAL, $signalHandler);
    }

        protected static function monitorWorkersForLinux()
        {
            static::$_status = static::STATUS_RUNNING;
            while (1) {
                // Calls signal handlers for pending signals.
                \pcntl_signal_dispatch();
                // Suspends execution of the current process until a child has exited, or until a signal is delivered
                $status = 0;
                $pid    = \pcntl_wait($status, \WUNTRACED);
                // Calls signal handlers for pending signals again.
                \pcntl_signal_dispatch();
                // If a child has already exited.
                if ($pid > 0) {
                    // Find out which worker process exited.
                    foreach (static::$_pidMap as $worker_id => $worker_pid_array) {
                        if (isset($worker_pid_array[$pid])) {
                            $worker = static::$_workers[$worker_id];
                            // Exit status.
                            if ($status !== 0) {
                                static::log("worker[" . $worker->name . ":$pid] exit with status $status");
                            }

                            // For Statistics.
                            if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) {
                                static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0;
                            }
                            ++static::$_globalStatistics['worker_exit_info'][$worker_id][$status];

                            // Clear process data.
                            unset(static::$_pidMap[$worker_id][$pid]);

                            // Mark id is available.
                            $id                              = static::getId($worker_id, $pid);
                            static::$_idMap[$worker_id][$id] = 0;

                            break;
                        }
                    }
                    // Is still running state then fork a new worker process.
                    if (static::$_status !== static::STATUS_SHUTDOWN) {
                        static::forkWorkers();
                        // If reloading continue.
                        if (isset(static::$_pidsToRestart[$pid])) {
                            unset(static::$_pidsToRestart[$pid]);
                            static::reload();
                        }
                    }
                }

                // If shutdown state and all child processes exited then master process exit.
                if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) {
                    static::exitAndClearAll();
                }
            }
        }

问:关于信号阻塞实现reload功能,如何保证「完成当前进程内任务后再退出」
答:在看这个解答前,先看下第二个问题,然后再回来。这个非常简单,
就是说reload只是参数,TA背后应该是收到这个参数后向某个进程发送
某个信号比如SIGUSR1。进程提前设置了SIGUSR1的mask,所以进程会首先
「兜住」SIGUSR1信号暂时不理会SIGUSR1(注意不是一直不理会),
一旦进程中UNBLOCK了这个信号,进程立马会相应这个信号,你让进程相应
该信号时候退出进程即可。顺序上就是先设置屏蔽SIGUSR1信号,进程开始
处理常规业务流程,处理完毕后解除屏蔽SIGUSR1信号。
有一定应用场景限制,适合进程中是一次性的任务。