perl
持っている人や友達がいると仮定すると、semop
SystemVセマフォを使用して子間の同期をとることができます。実用的なサンプルプログラムについては、以下を参照してください。
通常のフロントマターから始めます。このコードは、低レベルのセマフォ操作を直接呼び出すのではなく、組み込みの
IPC::SysVおよび
IPC::Semaphoreモジュールを使用します。
#! /usr/bin/env perl
use strict;
use warnings;
use IPC::Semaphore;
use IPC::SysV qw/ IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT /;
このプログラムは、子プロセスを2つの段階に分けます。最初の段階の子は、同期の心配なしに処理を実行して、完了するまで実行されます。これらの多くを任意に持つことができます。
第2段階のプロセスは1つですが、すべて
の第1段階の子が終了した後に実行されます。
以下は、単純なプレースホルダーの実装です。
# how many other children the last child must wait for
my $FIRST_STAGE_CHILDREN = 2;
sub first_stage {
my($id) = @_;
print "[$$] hello from child $id\n";
sleep rand 10;
print "[$$] child $id done\n";
}
sub second_stage {
print "[$$] hello from second-stage child!\n";
}
第1ステージと第2ステージの間の同期を実装するために、プログラムは、サイズが第1ステージの子の数に等しいセマフォのセットを作成します。第1段階の子が完了すると、プログラムはその子に対応する特定のセマフォを解放します。
my $sem = IPC::Semaphore->new(
IPC_PRIVATE, $FIRST_STAGE_CHILDREN,
S_IRUSR | S_IWUSR | IPC_CREAT)
or die "$0: failed to create semaphore: $!";
後で見るように、第2段階の子供は、セマフォをデクリメントしようとして、兄弟を待ちます。値をゼロから開始することにより、第2ステージの子がこれらのデクリメントを試行すると、OSは子をスリープ状態にします。すべての第1段階の子が終了してセマフォを解放した後でのみ、システムは第2段階の子のブロックを解除します。
# start in blocked state
$sem->setall((0) x $FIRST_STAGE_CHILDREN);
まず、私たちfork
は第一段階の子供たちです。この設計では、親プロセスが可能な限り多くの簿記を行います。first_stage
これにより、との定義がsecond_stage
単純に保たれます。また、第1ステージの子供がセマフォを解放せずに何らかの形で終了した場合、第2ステージは実行される見込みがありません。
my %kids;
foreach my $id (0 .. $FIRST_STAGE_CHILDREN - 1) {
my $pid = fork;
die "$0: fork: $!" unless defined $pid;
if ($pid) {
++$kids{$pid};
}
else {
first_stage $id;
$sem->op($id, 1, 0); # release
exit 0;
}
}
次に、第2段階の子をフォークします。重要:コードは複数のセマフォに対して操作を実行しますが、これはアトミックに発生します。つまり、すべてのセマフォに対して機能するか、まったく機能しないかのいずれかです。観察可能な状態では、第2段階がすべての第1段階のセマフォよりも少ない数を取得できたように見えます。これは重要なプロパティです。より複雑なシステムでは、無計画なワンジーツーシーの取得と解放はデッドロックを引き起こします。
my $pid = fork;
die "$0: fork: $!" unless defined $pid;
if ($pid) {
++$kids{$pid};
}
else {
# block waiting on all first-stage children
my @op = map +($_, -1, 0), 0 .. $FIRST_STAGE_CHILDREN - 1;
$sem->op(@op);
second_stage;
exit 0;
}
最後に、親プロセスはすべての子が完了するのを待ちます。
do {
$pid = waitpid -1, 0;
print "[$$] reaped $pid\n";
warn "$0: unknown child $pid" unless delete $kids{$pid};
} while $pid > 0 && keys %kids;
サンプル出力は以下のとおりです。一時停止を確認できるライブを見るのはもっと面白いです。
[18389]子供0からこんにちは
[18390]子供からこんにちは1
[18390]子1が完了しました
[18388]刈り取られた18390
[18389]子0が完了しました
[18391]第二段階の子供からこんにちは!
[18388]刈り取られた18389
[18388]刈り取られた18391