
場合によっては、プログラムが1つのインスタンスで動作することを保証する必要があります。 たとえば、特定のファイルを生成するスクリプトの場合があります。スクリプトの2つのコピーを同時に実行すると、ファイルが破損します。
起動中のプロセスがプログラムの現在実行中の唯一のインスタンスであるかどうか、またはすでに別のインスタンスが実行中かどうかを確認する必要がありますか?
このような検証には、信頼できるいくつかの方法があります。
主な方法
1)pidファイルの存在を確認します
スクリプトが起動し、pidファイルの存在を確認します。 pid-fileがすでに存在する場合、スクリプトの別のインスタンスが既に実行されていることを意味し、2回目に実行するべきではありません。 pidファイルが存在しない場合、スクリプトはpidファイルを作成し、動作を開始します。
問題は、pidファイルを削除せずに最初のインスタンスがクラッシュする可能性があることです。 起動されたスクリプトは常にpidファイルを検出し、それ自体を2番目のインスタンスと見なし、pidファイルが手動で削除されるまで実行を拒否するため、スクリプトを実行することはできなくなります。 さらに、ファイルの存在を確認してからこのファイルを作成することは、1つのアトミックではなく2つの別個の操作であるため、競合状態に問題があります。
2)プロセスリストでpidを確認する
スクリプトが起動し、pidファイルを読み取り、プロセステーブルに読み取りpidを持つプロセスがあるかどうかを確認します。 そのようなプロセスが存在する場合、それはスクリプトの別のインスタンスがすでに実行されていることを意味し、2度目に実行するべきではありません。 そのようなプロセスが存在しない場合、スクリプトはそのpidをpidファイルに書き込み、動作を開始します。
問題は、最初のインスタンスが落ちる可能性があり、それが機能したpidが別のプロセスに発行される可能性があることです。 その後、最初の方法と同様に、スクリプトの実行に問題があります。 もちろん、そのような状況の可能性は、pidがすぐに再度発行されないため、最初の場合よりもいくらか低くなります。 はい、また、無関係のプロセスがプロセスとまったく同じpidを受け取る可能性はそれほど大きくありませんが、pidの数は無限ではなく、円で発行されるためです。 最初の方法よりもさらに多くの操作があるため、条件の競合に加えて。
3)PIDファイルロック
スクリプトが実行され、pidファイルをロックしようとします。 ブロックできない場合は、スクリプトの別のインスタンスが既に実行中であり、2回目に実行しないでください。 pidファイルをロックできた場合、スクリプトは引き続き動作します。
この方法では、前の2つの方法で問題が発生することはありません。
- スクリプトの最初のインスタンスが落ちると、pidファイルが自動的にロック解除されるため、次のインスタンスを問題なく開始できます。
- ブロッキングはアトミックアクションであるため、条件の競合はありません。
したがって、このメソッドは、プログラムの2番目のインスタンスの起動をブロックすることが保証されています。
PIDロック方法
このメソッドの実装を詳細に検討してみましょう。
#!/usr/bin/perl use Carp; use Fcntl qw(:DEFAULT :flock); check_proc('/tmp/testscript.pid') and die " , !\n"; # , # # sleep 15; # sub check_proc { my ($file) = @_; my $result; sysopen LOCK, $file, O_RDWR|O_CREAT or croak " $file: $!"; if ( flock LOCK, LOCK_EX|LOCK_NB ) { truncate LOCK, 0 or croak " $file: $!"; my $old_fh = select LOCK; $| = 1; select $old_fh; print LOCK $$; } else { $result = <LOCK>; if (defined $result) { chomp $result; } else { carp " PID - $file"; $result = '0 but true'; } } return $result; }
まず、スクリプトはcheck_proc関数を呼び出し、実行中の別のインスタンスを確認します。検証が成功すると、スクリプトは対応するメッセージで停止します。
この行では、関数check_procとdieが条件演算子andを介して結合されていることに注意してください。 通常、このような接続詞はor演算子を介して作成されますが、この場合、接続詞のロジックは異なります。スクリプトに次のように伝えます。「あなたの存在の無意味さを認識して死にます!」
check_proc関数は、実際に実行中の場合は既に実行中のインスタンスのpidを返すか、undefを返します。 したがって、この関数の実行の真の結果は、プログラムの1つのインスタンスが既に実行中であり、2度目に実行する必要がないことを意味します。
関数check_proc
次に、check_proc関数を行ごとに分析します。
1)sysopen関数は、読み取りおよび書き込み用にファイルを開きます
ファイルを非破壊モードで開く必要があります。そうしないと、ファイルの内容が破壊されます。 このため、非破壊モードでファイルを開くことができないため、単純なオープン機能を使用できません。
O_RDWR | O_CREATフラグを指定したsysopen関数は、ファイルを非破壊モードで開きます。 O_RDWRフラグは読み取りと書き込みのために同時に開くことを意味し、O_CREATフラグは開くときにファイルが存在しない場合にファイルを作成します。 フラグはFcntlモジュールからインポートされます(フラグの数値を使用する場合、Fcntlなしで実行できます)。
2)flock関数はファイルをロックします
1つのプロセスのみがロックを持っていることを確認する必要があるため、排他ロックを要求する必要があります。 排他ロックは、LOCK_EXフラグで設定されます。 プロセスが排他ロックを受け取るとすぐに-それだけで、他の誰もそのようなロックを並行して取得できません。 これは、実際には、プログラムの2番目のインスタンスを開始するためのブロックメカニズムの基礎であり、重要な機能です。
flock関数は、他の誰かがすでにファイルをロックしていることを検出すると、ロックが解除されるまで待機します。 この動作は、検証には適していません。 ファイルが解放されるのを待つ必要はありません。ロックが検出された場合、すぐに肯定的な結果を返すcheck_proc関数が必要です。 これを行うには、フラグLOCK_NBを使用します。
さらなる動作は、ロックを取得できたか(3)または失敗したか(4)によって異なります。
3a)truncate関数はファイルをクリアします
ファイルを非破壊モードで開いたため、ファイルの古い内容は変更されませんでした。 このコンテンツは不要であり、干渉する可能性もあるため、ファイルをクリーンアップする必要があります。
3b)選択と変数の組み合わせ$ | バッファリングを無効にします
現在のプロセスのpidをpidファイルに書き込む必要があります。 しかし、ファイルへの出力はブロックごとにバッファリングされるため、pidが書き込まれます(そう思われます)が、実際には(今のところ)ファイル内では空です。 このため、実行中のプロセスのpidをpidファイルから読み取ろうとする他のプロセスは、そこで空を見つけます。 このチェックはpidファイルのブロックに基づいており、pidのチェックには基づいていないため、プロセスにとってpidがなくても大丈夫です。 ただし、PID自体を考慮するプロセスの場合、これは問題を引き起こします。
出力バッファリングを無効にするには、この出力のハンドルに関連付けられた$ |変数が必要です。 真の値に設定します。 最初の選択は、現在の記述子をpidファイルのハンドルに設定し、変数はtrueに設定され、2番目の選択はSTDOUTを現在の記述子に戻します。 その後、ファイルへの書き込みはバッファリングなしですぐに発生します。
4a)pidファイルからpidを読み取ります
ファイル自体からの読み取りは簡単ですが、ファイル内のpidが検出されない可能性があることに留意する必要があります。 これは、プログラムのインスタンスが既に実行されていることを意味します(結局、ロックを取得できませんでした)が、何らかの理由でこの実行中のインスタンスのpidが書き込まれませんでした。 これは、pidのチェックに基づいていないため、チェックでは問題になりません。 ただし、実行中のインスタンスが検出された場合、check_proc関数はtrue値を返す必要があるため、pidが欠落する代わりに、trueである他の何かを返す必要があります。
この場合の適切な値は、真のゼロです。 これは魔法の値(真珠には多くあります)であり、数値のコンテキストではゼロであり、ブール値ではtrueです。 真のゼロを書き込むためのいくつかのオプションがあります。オプション「0 but true」を使用します。
おわりに
pidファイルロック方式は、プログラムが単一のコピーで動作することを保証する最も信頼できる方法です。
check_proc関数とFcntlモジュールの接続は、別のモジュール(たとえば、MacLeod.pmという名前)に移動できます。この場合、1つのインスタンスでプログラムの操作が2行だけで実行されるようになります。
use MacLeod; check_proc('/tmp/testscript.pid') and die " , !\n";
または、チェックをもう少し詳細に行うことができます。
use MacLeod; my $pid = check_proc('/tmp/testscript.pid'); if ($pid) { die " $pid , !\n"; } else { print "!\n"; }
この場合、check_proc関数によって返される実行中のプロセスのpidは$ pid変数に書き込まれ、メッセージに表示できます。