それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

php のフォークと強制終了

単純なフォーク

<?php
$pid = pcntl_fork();
if ($pid == -1) {
  die('could not fork');
} else if ($pid) {
  // we are the parent
} else {
  // we are the child
  sleep(10);
  var_dump("We are the child:", $pid);
  exit;
}

実行結果

pcntl_wait($status); //Protect against Zombie children
var_dump("END :", $pid);

       \-+= 48201 takuya -bash
         \-+= 49054 takuya php fork.php
           \--- 49055 takuya php fork.php

49054 から 49055 が生えた

2個フォークした場合

<?php
$pid = pcntl_fork();
if ($pid == -1) {
  die('could not fork');
} else if ($pid) {
  // we are the parent
} else {
  // we are the child
  sleep(10);
  var_dump("We are the child:", $pid);
  exit;
}

$pid = pcntl_fork();
if ($pid == -1) {
  die('could not fork');
} else if ($pid) {
  // we are the parent
} else {
  // we are the child
  sleep(10);
  var_dump("We are the child1");
  exit;
}
pcntl_wait($status); //Protect against Zombie children
var_dump("END :", $pid);

実行中の状態

         \-+= 48802 takuya php fork.php
           |--- 48803 takuya php fork.php
           \--- 48804 takuya php fork.php

48802 から 48803, 48804 が生えた

フォークからさらにフォークした場合。

子プロセスから更に子プロセス、つまり孫プロセスを起動したら

<?php
$pid = pcntl_fork();
if ($pid == -1) {
  die('could not fork');
} else if ($pid) {
  // we are the parent
} else {
  // we are the child
  sleep(10);
  var_dump("We are the child:", $pid);
  exit;
}

$pid = pcntl_fork();
if ($pid == -1) {
  die('could not fork');
} else if ($pid) {
  // we are the parent
} else {
  // we are the child
  sleep(10);
  var_dump("We are the child1");
  exit;
}
pcntl_wait($status); //Protect against Zombie children
var_dump("END :", $pid);

この場合、子プロセスが孫より先に死ぬので孫が迷子になってしまう。孤児としてrootプロセスに引き取られる。

なのでちゃんと管理をしなくてはいけない。

fork 先でしっかり終了させる。つまり単純な作業に限定するとか、終了待ちをきちんとする。など

また、子プロセスが生成した子プロセスを見るには再帰して ps -o pid,ppid ax で 子プロセスpidを ppid にもつ孫を探すことになるかもしれない。

fork の注意点

fork を使う前に POSIX fork を知る必要がある。

fork のタイミングでメモリがコピーされますが、リソース(ファイルディスクリプタ)はコピーされると面倒が起きます。MySQLやHTTPなどのコネクションもfd(ファイルディスクリプタ)になってたりするので、fork 先でコネクションしないとエラーになることがある。

fork のコスト

fork するとメモリがコピーされるので、若干負荷が大きい操作になる。そこから先は別プロセスになるので、fork後のメモリの変更(変数の値変更)は別になる。

平行世界が作成されるイメージ

fork の利点

php でも時間がかかる処理は fork 先に任せて、メインは違うことに集中するなどと出来る。

コストは高いが、ジョブを待つだけの メインがあってジョブが来たら fork して仕事をやらせるみたいなことも出来る。

シグナルハンドラ

fork した両方のプロセスでシグナルをハンドリングするには、pcntl_signal_dispatchを入れてあげないといけないみたい

pcntl_signal_dispatch();の代わりに declare(ticks=1) を入れることで解決はしますが、tick を小さくするとパフォーマンスに影響します。

Ctrl+C(SIGINT)を押しても即時反応しない場合とかもdispatchまでいったんキューに入るからだと思います。

pcntl_signal_dispatch はその名前の通り、シグナルキューをそれぞれのハンドラに振り分けるものですね。

<?php


// signal handler function
function sig_handler($signo)
{
  echo "SIGNAL:" . $signo."\n";
  switch ($signo) {
    case SIGTERM:
      echo 'cactch SIGTERM'."\n";
      // handle shutdown tasks
      exit;
      break;
    case SIGINT:
      echo 'cactch SIGINT'."\n";
      exit;
    case SIGHUP:
      echo 'cactch SIGHUO'."\n";
      break;
    case SIGUSR1:
      echo "Caught SIGUSR1...\n";
      echo 'pid:'.posix_getpid();
      echo ' ppid:'.posix_getppid();
      break;
    default:
      echo 'default';
  }

}


pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGINT,  "sig_handler");
pcntl_signal(SIGHUP,  "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");

pcntl_signal_dispatch();

while (1){

  $a = 0;
  echo 'pid:'.posix_getpid()."\n";

  $pid = pcntl_fork();

  if ($pid===0) {
    // we are the child
    pcntl_signal_dispatch();
    sleep(5);
    var_dump("We are the child:", $pid);
    exit;
  }else{
    pcntl_signal_dispatch();

  }

  pcntl_wait($status); //Protect against Zombie children
  var_dump("waiting is end :", $pid);

}

linux の プロセス管理についてのおすすめ本

Linux のexecコールやfork とプロセス管理がしっかり分かる本だった。

参考資料

pcntl - What's the relation between declare(ticks) and a signal handler in php - Stack Overflow

http://qiita.com/ngyuki/items/a2b9d6d3011528d3becd