有没有办法以比 pcntl_alarm(int $seconds) 更细粒度的延迟来发出 SIGALRM 信号?

发布于 2025-01-15 10:14:54 字数 206 浏览 3 评论 0原文

pcntl_alarm(int $seconds) 仅具有秒的分辨率。 PHP 有没有办法以延迟(例如,毫秒)发出 SIGALRM 信号?也许是带有延迟参数的 posix_kill() ?

PS.:我知道 PECL 扩展 Swoole 中的 Swoole\Process::alarm(),但我正在寻找更简单的 PHP 解决方案。

pcntl_alarm(int $seconds) only has a resolution of seconds. Is there a way in PHP to signal a SIGALRM with a delay of, say, milliseconds? Maybe a posix_kill() with a delay argument?

PS.: I'm aware of Swoole\Process::alarm() from the PECL extension Swoole, but I'm looking for a more bare-bones PHP solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

小鸟爱天空丶 2025-01-22 10:14:54

我找到了一种方法来做到这一点,但它有点复杂:

<?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);
}

......而且它太消耗资源了。而且因为它每次都需要生成一个新进程,时间可能也不是很准确。


附录:
我设法通过仅创建一个长时间运行的中断程序进程而不是为每个中断请求创建短期运行的进程来提高其效率。

它距离理想还很远,但目前已经完成了工作:

<?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);
}

I found one way to do it, but it is a bit convoluted:

<?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);
}

...and it's way too resource intensive. And because it needs to spawn a new process each time probably not very time-accurate either.


Addendum:
I've managed to make it more efficient by creating only one long-running interrupter process, instead of creating short-running processes for each interruption request.

It's still far from ideal, but it does the job for now:

<?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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文