PHP Conference Japan 2024

pcntl_alarm

(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)

pcntl_alarm设置信号传递的闹钟

描述

pcntl_alarm(int $seconds): int

创建一个计时器,在给定秒数后向进程发送SIGALRM信号。任何对pcntl_alarm()的调用都会取消任何先前设置的闹钟。

参数

seconds

等待的秒数。如果seconds为零,则不会创建新的闹钟。

返回值

返回任何先前计划的闹钟在传递之前剩余的时间(以秒为单位),如果之前没有计划的闹钟,则返回0

添加注释

用户贡献的注释 4 个注释

kuba at valentine dot dev
4 年前
这就是你梦寐以求的通用超时功能,猜猜看 - 它非常可靠,基本上是牢不可破的。它可以中断你抛给它的任何东西,以及更多,你随便说 - socket_connect()、socket_read()、fread()、无限 while() 循环、sleep()、信号量 - 说真的,任何阻塞操作。您可以指定您自己的处理程序,并且可以轻松克服任何通常会使您的代码无响应的操作。

<?php
/**
* 因为我们不应该以同步方式处理异步
* 事件。
*/
pcntl_async_signals(TRUE);

/**
* 我们可以更改的一些标志,以确保
* 我们的操作超时。
*/
$timed_out = FALSE;

/**
* 注册 SIGALRM 信号处理程序,以避免
* 当信号到达时进程被杀死。
*/
pcntl_signal(SIGALRM, function($signal) use (&$timed_out) {
$timed_out = TRUE;
});

/**
* 现在我们将超时设置为 2 秒,但这并非一成不变的
* 我们可以随时调用 pcntl_alarm() 来延长或关闭它。
*/
pcntl_alarm(2);

/**
* 在这里,我们对结果不可预测的操作进行了一些处理,这些操作可能会
* 阻止我们的程序很长时间。
* 我喜欢将套接字作为示例,但它可以是任何东西。
*/
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$connection = socket_connect($socket, 'irc.ircnet.com', 6667);

/**
* 如果我们的阻塞操作没有超时,那么
* 计时器仍在计时,我们应该尽快将其关闭。
*/
$timed_out || pcntl_alarm(0);

/**
* 现在我们可以做任何我们想做的事情。
*/
$status = $connection ? '已连接。' : ($timed_out ? '超时。' : socket_strerror(socket_last_error($socket)));
echo
'状态:'. $status . PHP_EOL;
kuba at valentine dot dev
1 年前
请记住,您可以轻松地选择性地阻止您选择的信号类型(包括 SIGALRM)中断您不想中断的阻塞操作。使用 pcntl_sigprocmask() 来保护您代码的任何部分,无论何时需要,并且不会影响其其余部分的预期信号行为。

这是一个简单的示例,说明如何屏蔽一个阻塞 sleep 操作不被中断,同时允许它发生在另一个操作旁边 - 彼此紧挨着。

<?php
$alarm
= 2;
$sleep = 10;
$stamp = time();

pcntl_async_signals(TRUE);

/**
* 闹钟信号处理程序。
*/
pcntl_signal(SIGALRM, function(int $sig) use ($alarm, &$stamp) {
$late = (($now = time()) - $stamp) - $alarm;
$stamp = $now;
echo
'* 闹钟信号处理程序 * 触发 '. ($late ? ($late .' 秒延迟') : '按计划') .' *'. PHP_EOL;
pcntl_alarm($alarm);
});

/**
* 休眠函数。
*/
function get_some_sleep(int $duration, string $info) {
$start = time();
echo
PHP_EOL . $duration .' 秒休眠 - '. $info . PHP_EOL;
sleep($duration);
$early = $duration - (time() - $start);
echo
'休眠 '. ($early ? ('被中断。提前 '. $early .' 秒唤醒。') : '未被中断。') . PHP_EOL;
}

/**
* 在这里我们安排第一个闹钟。
*/
pcntl_alarm($alarm);

while (
TRUE) {
/**
* 此休眠可以并且会被闹钟信号中断。
*/
get_some_sleep($sleep, '无保护');

/**
* 现在我们设置我们的保护。从现在开始,被阻塞的信号将被忽略。
* 在保护开启时触发的每个被阻塞的信号类型都将被挂起。
*/
pcntl_sigprocmask(SIG_BLOCK, [SIGALRM]);
get_some_sleep($sleep, '受 sigprocmask() 保护');

/**
* 现在我们解除我们的保护。如果在保护开启时有任何被阻塞的信号触发
* 现在它的处理程序将立即执行一次。
* 这是一个非常有用的行为,因为我们保护了需要保护的东西,但也
* 不会错过任何东西 - 由于“每个类型一次”队列,不会被淹没。
*/
pcntl_sigprocmask(SIG_UNBLOCK, [SIGALRM]);
}
molsavsky1 at gmail dot com
2 年前
请注意,pcntl_signal 会中断 (u)sleep 调用,一旦处理程序完成,这些调用将不会恢复。

这是一种记录在案的行为 (https://php.net/manual/en/function.sleep.php),尽管在第一次遇到时它可能看起来像是一个错误。

来自文档
"如果调用被信号中断,sleep() 返回一个非零值。在 Windows 上,此值将始终为 192(Windows API 中 WAIT_IO_COMPLETION 常量的值)。在其他平台上,返回值将是剩余的休眠秒数。"

```
<?php

$interval
= 1;
pcntl_async_signals(true);

pcntl_signal(SIGALRM, function () use ($interval): void {
echo
'SIGALRM 调用' . PHP_EOL;
pcntl_alarm($interval);
});

pcntl_alarm($interval);

echo
'休眠(将被中断)开始' . PHP_EOL;

sleep(100000000000);

echo
'休眠由于中断而很快结束' . PHP_EOL;

$sleepTimeout = 10;
echo
"正确休眠 {$sleepTimeout} 秒" . PHP_EOL;

$startedAt = time();
while (
$sleepTimeout > 0 && ($sleepTimeout = sleep($sleepTimeout)) !== true) {
echo
"休眠中断,剩余 {$sleepTimeout} 秒" . PHP_EOL;
}

$elapsed = time() - $startedAt;
echo
"休眠在 {$elapsed} 秒后结束" . PHP_EOL;
```
Gao,Shengwei
6 年前
使用 pcntl_signal_dispatch() 捕获信号,不要使用 declare(ticks=1),因为它效率低下。

<?php
pcntl_signal
(SIGALRM, function () {
echo
'收到一个闹钟信号!' . PHP_EOL;
},
false);

pcntl_alarm(5);

while (
true) {
pcntl_signal_dispatch();
sleep(1);
}
To Top