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 个说明

33
kuba at valentine dot dev
3 年前
这就是您梦寐以求的通用超时功能,它与您想要的完全一样,而且它可靠得令人难以置信,它基本上是防弹的。它可以中断您扔给它的任何东西,甚至更多,您说出来——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;
5
kuba at valentine dot dev
1 年前
请记住,您可以轻松且选择性地阻止您不想中断的阻塞操作中断了您选择的信号类型(包括 SIGALRM)。使用 pcntl_sigprocmask() 来保护任何部分的代码,无论何时需要,都不会影响其剩余部分的预期信号行为。

这是一个屏蔽一个阻塞睡眠操作免受中断,同时允许它发生在另一个阻塞睡眠操作旁边的简单示例。

<?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]);
}
0
molsavsky1 at gmail dot com
2 年前
请注意,pcntl_signal 会中断 (u)sleep 调用,这些调用在处理程序完成后不会恢复。

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

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

```
<?php

$interval
= 1;
pcntl_async_signals(true);

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

pcntl_alarm($interval);

echo
'Sleep (will be interrupted) started' . PHP_EOL;

sleep(100000000000);

echo
'Sleep ended soon due to interrupt' . PHP_EOL;

$sleepTimeout = 10;
echo
"Proper sleep for {$sleepTimeout} seconds" . PHP_EOL;

$startedAt = time();
while (
$sleepTimeout > 0 && ($sleepTimeout = sleep($sleepTimeout)) !== true) {
echo
"Sleep interrupted, {$sleepTimeout} seconds remaining" . PHP_EOL;
}

$elapsed = time() - $startedAt;
echo
"Sleep finished after {$elapsed} seconds" . PHP_EOL;
```
-4
Gao,Shengwei
6 年前
使用 pcntl_signal_dispatch() 来捕获信号,不要使用 declare(ticks=1) 因为它效率低下。

<?php
pcntl_signal
(SIGALRM, function () {
echo
'Received an alarm signal !' . PHP_EOL;
},
false);

pcntl_alarm(5);

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