有没有办法用比 pcntl_alarm(int $seconds) 更细粒度的延迟来发送 SIGALRM 信号?
Is there a way to signal a SIGALRM with a finer-grained delay than pcntl_alarm(int $seconds)?
pcntl_alarm(int $seconds)
只有秒的分辨率。在 PHP 中有没有办法以毫秒的延迟发出 SIGALRM 信号?也许 posix_kill()
带有延迟参数?
PS.: 我知道 Swoole\Process::alarm()
来自 PECL 扩展 Swoole,但我正在寻找更简单的 PHP 解决方案。
我找到了一种方法,但有点复杂:
<?php
// alarm uses proc_open() to signal this process from a child process
function alarm(int $msec): void {
$desc = [
['pipe', 'r']
];
$pid = posix_getpid();
$process = proc_open('php', $desc, $pipes);
fwrite(
$pipes[0],
"<?php
usleep($msec * 1000);
posix_kill($pid, SIGALRM);
"
);
fclose($pipes[0]);
}
function handleSignal(int $signal): void {
switch($signal) {
case SIGALRM:
echo "interrupted by ALARM\n";
break;
}
}
pcntl_async_signals(true);
pcntl_signal(SIGALRM, 'handleSignal');
// set alarm 200ms from now
alarm(200);
while(true) {
echo "going to sleep for 10 seconds...\n";
// first sleep(10) will be interrupted after 200ms
sleep(10);
}
...这太耗费资源了。而且因为它每次都需要生成一个新进程,所以可能也不是很 time-accurate。
附录:
我设法通过只创建一个 long-running 个中断进程来提高效率,而不是为每个中断请求创建 short-running 个进程。
它离理想还有很远的距离,但它现在可以完成工作了:
<?php
// long-running interrupter process for the Interrupter class
// that accepts interruption requests with a delay
class InterrupterProcess
{
private $process;
private $writePipe;
private const PROCESS_CODE = <<<'CODE'
<?php
$readPipe = fopen('php://fd/3', 'r');
$interrupts = [];
while(true) {
$r = [$readPipe];
$w = null;
$e = null;
$time = microtime(true);
$minExpiry = min($interrupts + [($time + 1)]);
$timeout = $minExpiry - $time;
if(stream_select($r, $w, $e, (int) $timeout, (int) (fmod($timeout, 1) * 1e6)) > 0) {
$interrupt = json_decode(fread($readPipe, 1024), true);
$interrupts[$interrupt['pid']] = $interrupt['microtime'];
}
$time = microtime(true);
foreach($interrupts as $pid => $interrupt) {
if($interrupt <= $time) {
posix_kill($pid, SIGALRM);
unset($interrupts[$pid]);
}
}
}
CODE;
public function __construct() {
$desc = [
['pipe', 'r'],
STDOUT,
STDOUT,
['pipe', 'r']
];
$this->process = proc_open(['php'], $desc, $pipes);
$this->writePipe = $pipes[3];
fwrite($pipes[0], self::PROCESS_CODE);
fclose($pipes[0]);
}
public function __destruct() {
$this->destroy();
}
public function setInterrupt(int $pid, float $delay): bool {
if(!is_null($this->writePipe)) {
fwrite($this->writePipe, json_encode(['pid' => $pid, 'microtime' => microtime(true) + $delay]));
return true;
}
return false;
}
private function destroy(): void {
if(!is_null($this->writePipe)) {
fclose($this->writePipe);
$this->writePipe = null;
}
if(!is_null($this->process)) {
proc_terminate($this->process);
proc_close($this->process);
$this->process = null;
}
}
}
// main Interrupter class
class Interrupter
{
private $process;
public function __destruct() {
$this->destroy();
}
public function interrupt(float $delay): void {
if(is_null($this->process)) {
pcntl_async_signals(true);
pcntl_signal(SIGALRM, function(int $signal): void {
$this->handleSignal($signal);
});
$this->process = $this->createInterrupterProcess();
}
$this->process->setInterrupt(posix_getpid(), $delay);
}
private function createInterrupterProcess(): InterrupterProcess {
return new InterrupterProcess();
}
private function handleSignal(int $signal): void {
switch($signal) {
case SIGALRM:
$time = time();
echo "interrupted by ALARM @ $time\n";
break;
}
}
private function destroy(): void {
$this->process = null;
}
}
$interrupter = new Interrupter();
while(true) {
$time = time();
echo "going to sleep for 10 seconds @ $time...\n";
$interrupter->interrupt(2);
sleep(10);
}
pcntl_alarm(int $seconds)
只有秒的分辨率。在 PHP 中有没有办法以毫秒的延迟发出 SIGALRM 信号?也许 posix_kill()
带有延迟参数?
PS.: 我知道 Swoole\Process::alarm()
来自 PECL 扩展 Swoole,但我正在寻找更简单的 PHP 解决方案。
我找到了一种方法,但有点复杂:
<?php
// alarm uses proc_open() to signal this process from a child process
function alarm(int $msec): void {
$desc = [
['pipe', 'r']
];
$pid = posix_getpid();
$process = proc_open('php', $desc, $pipes);
fwrite(
$pipes[0],
"<?php
usleep($msec * 1000);
posix_kill($pid, SIGALRM);
"
);
fclose($pipes[0]);
}
function handleSignal(int $signal): void {
switch($signal) {
case SIGALRM:
echo "interrupted by ALARM\n";
break;
}
}
pcntl_async_signals(true);
pcntl_signal(SIGALRM, 'handleSignal');
// set alarm 200ms from now
alarm(200);
while(true) {
echo "going to sleep for 10 seconds...\n";
// first sleep(10) will be interrupted after 200ms
sleep(10);
}
...这太耗费资源了。而且因为它每次都需要生成一个新进程,所以可能也不是很 time-accurate。
附录:
我设法通过只创建一个 long-running 个中断进程来提高效率,而不是为每个中断请求创建 short-running 个进程。
它离理想还有很远的距离,但它现在可以完成工作了:
<?php
// long-running interrupter process for the Interrupter class
// that accepts interruption requests with a delay
class InterrupterProcess
{
private $process;
private $writePipe;
private const PROCESS_CODE = <<<'CODE'
<?php
$readPipe = fopen('php://fd/3', 'r');
$interrupts = [];
while(true) {
$r = [$readPipe];
$w = null;
$e = null;
$time = microtime(true);
$minExpiry = min($interrupts + [($time + 1)]);
$timeout = $minExpiry - $time;
if(stream_select($r, $w, $e, (int) $timeout, (int) (fmod($timeout, 1) * 1e6)) > 0) {
$interrupt = json_decode(fread($readPipe, 1024), true);
$interrupts[$interrupt['pid']] = $interrupt['microtime'];
}
$time = microtime(true);
foreach($interrupts as $pid => $interrupt) {
if($interrupt <= $time) {
posix_kill($pid, SIGALRM);
unset($interrupts[$pid]);
}
}
}
CODE;
public function __construct() {
$desc = [
['pipe', 'r'],
STDOUT,
STDOUT,
['pipe', 'r']
];
$this->process = proc_open(['php'], $desc, $pipes);
$this->writePipe = $pipes[3];
fwrite($pipes[0], self::PROCESS_CODE);
fclose($pipes[0]);
}
public function __destruct() {
$this->destroy();
}
public function setInterrupt(int $pid, float $delay): bool {
if(!is_null($this->writePipe)) {
fwrite($this->writePipe, json_encode(['pid' => $pid, 'microtime' => microtime(true) + $delay]));
return true;
}
return false;
}
private function destroy(): void {
if(!is_null($this->writePipe)) {
fclose($this->writePipe);
$this->writePipe = null;
}
if(!is_null($this->process)) {
proc_terminate($this->process);
proc_close($this->process);
$this->process = null;
}
}
}
// main Interrupter class
class Interrupter
{
private $process;
public function __destruct() {
$this->destroy();
}
public function interrupt(float $delay): void {
if(is_null($this->process)) {
pcntl_async_signals(true);
pcntl_signal(SIGALRM, function(int $signal): void {
$this->handleSignal($signal);
});
$this->process = $this->createInterrupterProcess();
}
$this->process->setInterrupt(posix_getpid(), $delay);
}
private function createInterrupterProcess(): InterrupterProcess {
return new InterrupterProcess();
}
private function handleSignal(int $signal): void {
switch($signal) {
case SIGALRM:
$time = time();
echo "interrupted by ALARM @ $time\n";
break;
}
}
private function destroy(): void {
$this->process = null;
}
}
$interrupter = new Interrupter();
while(true) {
$time = time();
echo "going to sleep for 10 seconds @ $time...\n";
$interrupter->interrupt(2);
sleep(10);
}