プロが教えるわが家の防犯対策術!

fork()の挙動がいまいち良く分からないので質問させてください。
自分はPHPでなのですが、fork()はCで使われるのが多いだろうということと、概念を知りたいとの事でこちらで質問させていただきました。

以下のように書きLinuxの端末にて実行しました。
目的は、
1. 2つの子プロセスを作り、それらを同時並行処理したい
2. 同時並行処理なので3秒後に処理を終わらせて出力したい
という事です。


#!/usr/local/bin/php -q
<?php
$time = time();
$pid = pcntl_fork();
if ($pid == 0) {
$j;
for ($j=0; $j < 3; $j++) {
printf("child1: %d\n", $j);
sleep(1);
}
} else if($pid > 0) {
pcntl_wait($status);
print ("Parent-a\n");
} else {
die('fork できません');
}
$pid = pcntl_fork();
if ($pid == 0) {
$i;
for ($i=0; $i < 3; $i++) {
printf("child2: %d\n", $i);
sleep(1);
}
} else if($pid > 0) {
pcntl_wait($status);
print ("Parent-b\n");
} else {
die('fork できません');
}
echo "time:" . (time() - $time) . "sec\n";

すると、6秒後にまず
child1: 0
child1: 1
child1: 2
child2: 0
child2: 1
child2: 2
time:6sec
child1: 0
child1: 1
child1: 2
Parent-b
time:6sec
が出力され、その3秒後に
Parent-a
child2: 0
child2: 1
child2: 2
time:9sec
Parent-a
Parent-b
time:9sec
が出力されました。

分からない点は以下の通りです。

1. Parent-bがParent-aより前に表示されたり、
child1: 0 child1: 1 child1: 2 child2: 0 child2: 1 child2: 2
及びParent-a Parent-bを出力したいのに、
なんかそれ以外のものが色々と不思議な順序で出力されている上、
9秒も処理時間にかかり、並行処理ではなく逐次処理になっているように見える。

2. 確か"Unix/Linuxプログラミング理論と実践"だったと思うのですが、Unix系の本にて
子作成(親のコピー) -> 子処理中、親は居眠り -> 子exit() -> 親wait() -> 親起きる
という感じで書かれていたように思いますが、
実行例のParent-a等を見ると、挙動が分かりません。

長くなり申し訳ございませんが、もし宜しければ間違っている点をご指摘していただけないでしょうか?
また、上記に書いた"目的"を実現するためのCなどで宜しいですのでコード例など部分だけで宜しいですので挙げて頂けたら幸いです。

申し訳ございませんが、宜しくお願いいたします。

A 回答 (4件)

他の方が回答されているように、


親子の両プロセスがそれぞれ2回目のfork()で枝分かれしている点、
それから1つ目の子プロセスを wait() してから、
2つ目の子プロセスを fork() している点が問題です。

子プロセスの処理を関数にして分離すると
わかりやすいかもしれません。


--
function child_proc($no){
 for( $i = 0; $i < 3; $i++ ){
  printf( "child%d: %d\n", $no, $i ); sleep( 1 );
 }
 exit( 100 + $no );
}

$pid1 = pcntl_fork();
if( $pid1 < 0 ){ die( "fork 1: error." ); }
if( $pid1 == 0 ){ child_proc( 1 ); }

$pid2 = pcntl_fork();
if( $pid2 < 0 ){ die( "fork 2: error." ); }
if( $pid2 == 0 ){ child_proc( 2 ); }

pcntl_waitpid( $pid1, $s );
printf( "(exit1) %d\n", pcntl_wexitstatus( $s ) );
pcntl_waitpid( $pid2, $s );
printf( "(exit2) %d\n", pcntl_wexitstatus( $s ) );
    • good
    • 0
この回答へのお礼

ありがとうございましたッ!
ちょっと変更しましたが、同様の記述をして、無事に自分のやりたかった事を実現できました。
本当にありがとうございました。

お礼日時:2012/01/18 16:18

>親プロセスはchild2のように子プロセス処理が終わる度に、


>"pid>0"の処理を上から行う事になるのでしょうか?

child2の動作を誰もそういう風に説明してないですが・・・
前の説明よく見直してください。
説明としては#1のが詳しいですからそちらもよく読んでください。

>"Parent-a"が何故2回も出力されるのか分かりません。

バッファリングが関係しそうな気はしますがphpのバッファリング制御の事は
私わかりませんので、頑張って調べてください。

>また、時間も6秒かかっております。

child1が終了する(開始から3秒後)のを待ってから
child2をforkするように書かれてますので最低でも6秒かかるのは当然ですよ。

child1とchild2を並列動作させたいならCだとこんな感じ

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
pid_t child_pids[2];

for (int i = 0; i < sizeof(child_pids) / sizeof(child_pids[0]); i++) {
child_pids[i] = fork();
if ((int)child_pids[i] == -1) {
perror("fork()");
return 1;
}
else if (child_pids[i] == 0) {
for (int j = 0; j < 3; ++j) {
printf("child%d: %d\n", i, j);
sleep(1);
}
_exit(0);
}
else {
/* no-op */
}
}

while (1) {
pid_t pid;
int status;
int finished;

finished = 0;
pid = wait(&status);
for (int i = 0; i < sizeof(child_pids) / sizeof(child_pids[0]); i++) {
if (child_pids[i] == pid)
child_pids[i] = 0;
if (child_pids[i] == 0)
finished++;
}
if (finished == sizeof(child_pids) / sizeof(child_pids[0]))
break;
}

return 0;
}
    • good
    • 0
この回答へのお礼

>child2の動作を誰もそういう風に説明してないですが・・・
すみません、Parent-aが2度も出来てきたので、そうなのかなぁ、と思いまして書かせていただきました

コードの記述、ありがとうございました。
やはり、wait()の記述がミソだと言うことが分かりました。
本当にありがとうございました。

お礼日時:2012/01/18 16:21

>2. 確か"Unix/Linuxプログラミング理論と実践"だったと思うのですが、Unix系の本にて


>子作成(親のコピー) -> 子処理中、親は居眠り -> 子exit() -> 親wait() -> 親起きる
>という感じで書かれていたように思いますが、

その書籍は読んだことありませんが、
「子処理中、親は居眠り」はおかしいですね。
「親wait()で子exit()まで居眠り」が正しいです。

>実行例のParent-a等を見ると、挙動が分かりません。

子exit()は明示的に書かないといけないですよ?
例に書かれているものはそれがないので
child1がchild2のfork()までしてます。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございました。
確かにサンプルではexit()が抜けておりました。
ご指摘ありがとうございます。

$pid1 = pcntl_fork();
if ($pid1 == 0) {
$j;
for ($j=0; $j < 3; $j++) {
printf("child1: %d\n", $j);
sleep(1);
}
echo "現在のプロセスID=" . posix_getpid() . "\n";
exit();
} else if($pid1 > 0) {
pcntl_wait($status);
print ("Parent-a\n");
} else {
die('fork できません');
}
のように変えたら挙動が変わりました。

しかし、
child1: 0 child1: 1 child1: 2
現在のプロセスID=4504
Parent-a
child2: 0 child2: 1 child2: 2
現在のプロセスID=4505
Parent-a Parent-b
time:6sec
のようになります。

親プロセスはchild2のように子プロセス処理が終わる度に、
"pid>0"の処理を上から行う事になるのでしょうか?
"Parent-a"が何故2回も出力されるのか分かりません。

また、時間も6秒かかっております。
この場合、どの部分にchild2の処理(pid2=fork()以下)を書けば3秒で終わるようになるのでしょうか?

お礼日時:2012/01/15 15:09

動きは多分こんなところ。

具体的なプロセスIDを表示するようにしたら、デバッグが楽になると思ます。

親プロセス
 |
【分岐】―子プロセス1
 |    |
【待ち】  |
 :   【処理1】
 :    |
 :   【分岐】―子プロセス2
 :    |    |
 :   【待ち】  |
 :    :    |
 :    :   【処理2】
 :    :    |
 :   【再開】←【時間表示後終了】
 :    |
【再開】←【時間表示後終了】
 |
【分岐】―子プロセス3
 |    |
【待ち】  |
 :   【処理2】
 :    |
【再開】←【時間表示後終了】
 |
【時間表示後終了】
「fork()の挙動について質問させてくだ」の回答画像1
    • good
    • 0
この回答へのお礼

非常に分かりやすい図を記述して頂きまして有難うございました。
視覚的に理解できました。

No.2でのwormholeさんのご指摘にあったようにexit();を加えたら変な挙動はおさまりました。
ただ、Parent-aが2回表示されているのですが、何故このような挙動になるのかは分かりません。
上記図のように、親プロセスは上から下へと向かうのではなく、wait()の際かなにかに戻るような事もあるのでしょうか?

それと、child1とchild2を同時並列処理をさせるには、親プロセスの
最初の【待ち】--->【再開】の中に入れこまなくてはいけないのではと思いますが、
fork()した、その下にもう一度fork()を2回打って・・・とか考えたのですが、頭が少し混乱してきました(苦笑)

お礼日時:2012/01/15 15:27

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!