PHPでの競合状態との戦い

「競合状態」などのエラーは、負荷の軽いプロジェクトではめったに見つかりません。負荷が増えると、状況はゆっくりですが確実に変わります。 そして、たとえば次のように、ファイル内のデータの通常のキャッシングが完了したら:



function getFlagFromFile($filename) { if (file_exists($filename)) { if (!$this->validate()) { //     ? unlink($filename); return false; } else { return file_get_contents($filename); } } return false; }
      
      





unlink()行でエラーを生成します:ファイル$ filenameは存在しません!



最も興味深い理解できないことは、エラーがランダムな時点で発生し、デバッグしようとしたときに再現されないことです!



競合状態エラーは、同じコードが同時に実行されるシステムの状態で発生します(複数の並列スレッド)。 上記の例では、コードが複数のスレッドで実行される場合、file_exist($ filename)および!$ This-> validate()のチェックを両方のスレッドで同時に実行できますが、1つのスレッドはunlink($ filename)よりも早く実行できます別の-そして、2番目のスレッドはエラーを引き起こします。



同じサーバー内でこのエラーに対処する2つの方法を見てみましょう。flock()ファイルロックを使用しません(コードの重要なセクションの複数の要求で、ファイルシステムへの不要な呼び出しは最善の方法ではありません)。



APCとセマフォを使用して、レースの状態を妨げることができます-そして、対応するアトミック操作があります。 しかし、順番にやってみましょう。



APCソリューション



PHPの代替キャッシュ(代替PHPキャッシュ-APC)は、実行されたスクリプトのバイトコードをキャッシュします。これにより、ソースコードを分析するためのリソースのコストを防ぐことができます。 しかし、APCが独自のキー値ストレージを持っていることを誰もが知っているわけではありません。その特徴は、リクエスト間で値を保存することです。 設定が完了すると、Webサーバーが再起動されるか、値が強制的に削除されるまで(または値が設定されている場合は値が期限切れになります)、値はAPCに保存されます。



排他的ロックの場合、apc_add関数(キー、値、ライフタイム)を使用します-値がすでに割り当てられている場合はfalseを返します(キーに値を再割り当てするためのapc_store()が存在します)。 ソリューションを使用するための完全な条件は次のとおりです(getFlagFromFile()への変更にはコメント***が付いています):



 function canUseApc() { return extension_loaded('apc') && ini_get('apc.enabled') && php_sapi_name() !== 'cli'; } function getFlagFromFile($filename) { if (file_exists($filename)) { if (!$this->validate()) { if ($this->canUseApc() && apc_add('some_key', 1)) { //*** unlink($filename); apc_delete('some_key'); //*** } return false; } else { return file_get_contents($filename); } } return false; }
      
      







ここでのファイルの削除は、スレッドがAPCで値を設定できた場合にのみ実行されます。これは、並列削除とエラーが発生しないことを意味します。 ただし、apc_delete()を使用してAPCから値を削除するのを忘れた場合、Webサーバーを再起動するだけで削除できます。



これは最も簡単な実装ソリューションです。 ただし、ソリューションの主な欠点は、APCがCLIスクリプトに対して機能しないことです。 彼らにとっては、セマフォの解決策が適しています。



セマフォソリューション



セマフォは、変数(フラグ)として想像するのが容易であり、増減させることができます。 セマフォが1つのプロセスによってキャプチャされると、その値は1つ減少し、解放されると1つ増加します。 さらに、セマフォの現在の値がゼロの場合、プロセスはそれをキャプチャできず、セマフォが解放されるのを待ちます。



セマフォリソースを取得するには、sem_get関数を使用します(整数識別子、セマフォ値= 1)。 関数を使用すると、ユニティとは異なる値を持つセマフォを取得できます。その後、複数のスレッドがセマフォをキャプチャできます。 実際には、sem_acquire関数(セマフォリソース)がキャプチャに使用され、キャプチャが成功した場合はtrueを返し、そうでない場合はfalseを返します。



セマフォを使用する場合の例は次のようになります。



 function getFlagFromFile($filename) { if (file_exists($filename)) { if (!$this->validate()) { $sem = sem_get(1); //*** if (sem_acquire($sem) && file_exists($filename)) { //*** unlink($filename); } sem_remove($sem); //*** return false; } else { return file_get_contents($filename); } } return false; }
      
      







UPD:FrenzyKrygerは、sem_remove($ sem)は無条件でなければならないことを正しく指摘しました。



注意深い読者は、ファイルの存在を再確認したことに気付くでしょう。 最初のスレッドがセマフォをキャプチャし、ファイルを削除してセマフォを解放すると、2番目のスレッドは実行を継続できますが、ファイルを削除する必要はありません。ファイルは存在しないため、再度チェックを追加します。



ビジー状態のセマフォを解放するsem_remove関数(セマフォリソース)は、ここで重要な役割を果たします。 セマフォが解放されない場合、並列スレッドは現在のスレッドが終了するまでスタンバイ状態のままになります。



これはこのソリューションのマイナスです。並列スレッドが待機できる状況は常に許容できるとは限りません。 多くの場合、サーバーは、必要なアクションを完了できなかったとしても、できるだけ早く回答を行い、排他的アクセスを待たないようにする必要があります。 さらに、以前のソリューションと比較して、セマフォはcliスクリプトで機能します。



まとめると



考慮される各メソッドには、マイナスとプラスがあります。 両方のソリューションを1つのクラスに組み合わせて、直接実装を隠すのが最も便利です。 次に、レースの状態を防ぐための優れたシンプルなツールを取得します。



 function getFlagFromFile($filename) { if (file_exists($filename)) { if (!$this->validate()) { if ($race = RaceCondition::prevent('FLAG_'.$filename)) { //*** unlink($filename); $race->release(); //*** } return false; } else { return file_get_contents($filename); } } return false; }
      
      







完全なソリューションをアップロードせず、宿題として残します=)シンプルなグーグルは、セマフォ、スレッド、およびAPCに関するより詳細な回答を迅速に提供します。



PMではコメントと編集を歓迎します!



All Articles