PHP では、flock はあまりうまく機能しないようです。複数の同時実行の場合、リソースが独占されてすぐに解放されないか、まったく解放されないことが多く、デッドロックが発生し、サーバーの CPU 使用率が非常に高くなり、場合によってはサーバーが完全に停止することもあるようです。これは多くの Linux/UNIX システムで発生するようです。したがって、flock を使用する前に慎重に検討する必要があります。
flock() を適切に使用すれば、デッドロックの問題を解決することは完全に可能です。もちろん、flock() 関数の使用を考慮しない場合でも、問題に対する適切な解決策はあります。解決策を大まかにまとめると以下のようになります。
オプション 1: ファイルをロックするときにタイムアウトを設定します。大まかな実装は以下の通りです:
if($fp=fopen($fileName,'a')){ $startTime=microtime(); do{ $canWrite=flock($fp,LOCK_EX); if(!$canWrite){ usleep(round(rand(0,100)*1000)); } }while((!$canWrite)&&((microtime()-$startTime)<1000)); if($canWrite){ fwrite($fp,$dataToSave); } fclose($fp); }
タイムアウトは1msに設定されており、この時間内にロックが取得できなかった場合は、当然ながらファイルの操作権を取得するまで繰り返し取得されます。タイムアウト制限に達した場合は、すぐに終了してロックを放棄し、他のプロセスが動作できるようにする必要があります。
オプション 2: flock 関数を使用せず、一時ファイルを使用して読み取りと書き込みの競合の問題を解決します。一般原則は次のとおりです:
(1) 更新する必要があるファイルのコピーを一時ファイル ディレクトリに置き、ファイルの最終変更時刻を変数に保存し、この一時ファイルにランダムな困難なファイルを与えます。 -リピートするファイル名。
(2) この一時ファイルを更新した後、元のファイルの最終更新時刻が以前に保存した時刻と一致しているかどうかを確認します。
(3) 最終変更時刻が同じ場合、変更された一時ファイルの名前を元のファイルに変更します。ファイルのステータスが同期して更新されるようにするには、ファイルのステータスをクリアする必要があります。
(4) ただし、最終変更時刻が以前に保存された時刻と一致する場合は、この期間中に元のファイルが変更されたことを意味し、この時点で一時ファイルを削除する必要があり、それを示す false を返します。現時点では、ファイルには他の変更が加えられています。プロセスは実行中です。
実装コードは次のとおりです:
$dir_fileopen='tmp'; function randomid(){ return time().substr(md5(microtime()),0,rand(5,12)); } function cfopen($filename,$mode){ global $dir_fileopen; clearstatcache (); do{ $id=md5(randomid(rand(),TRUE)); $tempfilename=$dir_fileopen.'/'.$id.md5($filename); } while( file_exists ($tempfilename)); if(file_exists($filename)){ $newfile=false; copy($filename,$tempfilename); }else{ $newfile=true; } $fp=fopen($tempfilename,$mode); return $fp?array($fp,$filename,$id,@ filemtime ($filename)):false; } function cfwrite($fp,$string){ return fwrite($fp[0],$string); } function cfclose($fp,$debug='off'){ global $dir_fileopen; $success=fclose($fp[0]); clearstatcache(); $tempfilename=$dir_fileopen.'/'.$fp[2].md5($fp[1]); if((@filemtime($fp[1])==$fp[3])||($fp[4]==true&&!file_exists($fp[1]))||$fp[5]==true){ rename($tempfilename,$fp[1]); }else{ unlink($tempfilename); //说明有其它进程 在操作目标文件,当前进程被拒绝 $success=false; } return $success; } $fp=cfopen('lock.txt','a+'); cfwrite($fp,"welcome to beijing.\n"); fclose($fp,'on');
上記のコードで使用されている関数について説明する必要があります:
(1) rename(); この関数は実際には Linux の mv に似ています。ファイルやディレクトリのパスや名前を更新すると便利です。ただし、ウィンドウで上記のコードをテストすると、新しいファイル名がすでに存在する場合、現在のファイルがすでに存在するという通知が表示されます。ただし、Linux では正常に動作します。
(2) clearstatcache(); ファイルのステータスをクリアします。PHP はパフォーマンスを向上させるためにすべてのファイルの attribute 情報をキャッシュしますが、複数のプロセスがファイルを削除または更新するときに、PHP がキャッシュを更新する時間がない場合があります。その中のファイル属性により、アクセスされた最終更新時刻が実際のデータではないという事実が簡単に判明する可能性があります。したがって、ここではこの関数を使用して保存されたキャッシュをクリアする必要があります。
オプション 3: 同時実行の可能性を減らすために、操作されたファイルをランダムに読み書きします。
このソリューションは、ユーザーのアクセスログを記録するときによく使用されるようです。以前は、ランダムなスペースを定義する必要がありましたが、スペースが大きいほど、同時実行の可能性は低くなり、ランダムな読み取りおよび書き込みスペースが [1 ~ 500] であると仮定すると、ログ ファイルの分布範囲は log1 から log500 になります。ユーザーがアクセスするたびに、log1 ~ log500 までの任意のファイルにデータがランダムに書き込まれます。同時に、2 つのプロセスがログを記録しています。プロセス A は更新された log32 ファイルである可能性がありますが、プロセス B はどうなるでしょうか。このときの更新は log399 になる可能性があります。プロセス B にも log32 を実行させたい場合、確率は基本的に 1/500 であり、ほぼゼロに等しいことを知っておく必要があります。アクセスログを分析する必要がある場合、これらのログをマージしてから分析するだけで済みます。このソリューションを使用してログを記録する利点の 1 つは、プロセス操作がキューに入れられる可能性が比較的小さく、プロセスが各操作を非常に迅速に完了できることです。
オプション 4: 操作するすべてのプロセスをキューに入れます。 次に、ファイル操作を完了するための専用サービスを配置します。キュー内の除外された各プロセスは最初の特定の操作に相当するため、ここに多数のファイル操作プロセスがある場合、初めてサービスはキューから特定の操作アイテムを取得するだけで済みます。列の最後尾に並んでください。列に並ぶ意思がある限り、列の長さは関係ありません。
前述のオプションには、それぞれ独自の利点があります。これは大まかに 2 つのカテゴリに要約できます:
(1) オプション 1、2、4 などのキューイングが必要 (影響は遅い)
(2) キューイングは必要ありません。 (ファーストインパクト) オプション 3
キャッシュ システム を設計する場合、通常、オプション 3 は採用されません。案3は解析プログラムと書き込みプログラムが同期していないため、書き込みの際に解析の難易度は全く考慮されず、書き込みがよければ問題ありません。想像してみてください。キャッシュの更新時にランダムなファイルの読み書きも行うと、キャッシュの読み込み時に多くの処理が追加されそうです。ただし、オプション 1 と 2 はまったく異なりますが、書き込み時間はかかりますが (ロックの取得に失敗した場合は繰り返し取得されます)、ファイルの読み取りは非常に便利です。キャッシュを追加する目的は、データ読み取りのボトルネックを軽減し、それによってシステムのパフォーマンスを向上させることです。
以上がPHP の読み取りおよび書き込みファイルの競合に対する高同時実行ソリューションの概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。