APC で変数を更新しようとしていますが、多くのプロセスがそれを実行しようとしています。
APC はロック機能を提供していないため、他のメカニズムを使用することを検討しています...これまでに見つかったのは、mysql の GET_LOCK() と php の flock() です。他に検討する価値はありますか?
更新: sem_acquire を見つけましたが、ブロッキング ロックのようです。
/*
CLASS ExclusiveLock
Description
==================================================================
This is a pseudo implementation of mutex since php does not have
any thread synchronization objects
This class uses flock() as a base to provide locking functionality.
Lock will be released in following cases
1 - user calls unlock
2 - when this lock object gets deleted
3 - when request or script ends
==================================================================
Usage:
//get the lock
$lock = new ExclusiveLock( "mylock" );
//lock
if( $lock->lock( ) == FALSE )
error("Locking failed");
//--
//Do your work here
//--
//unlock
$lock->unlock();
===================================================================
*/
class ExclusiveLock
{
protected $key = null; //user given value
protected $file = null; //resource to lock
protected $own = FALSE; //have we locked resource
function __construct( $key )
{
$this->key = $key;
//create a new resource or get exisitng with same key
$this->file = fopen("$key.lockfile", 'w+');
}
function __destruct()
{
if( $this->own == TRUE )
$this->unlock( );
}
function lock( )
{
if( !flock($this->file, LOCK_EX | LOCK_NB))
{ //failed
$key = $this->key;
error_log("ExclusiveLock::acquire_lock FAILED to acquire lock [$key]");
return FALSE;
}
ftruncate($this->file, 0); // truncate file
//write something to just help debugging
fwrite( $this->file, "Locked\n");
fflush( $this->file );
$this->own = TRUE;
return TRUE; // success
}
function unlock( )
{
$key = $this->key;
if( $this->own == TRUE )
{
if( !flock($this->file, LOCK_UN) )
{ //failed
error_log("ExclusiveLock::lock FAILED to release lock [$key]");
return FALSE;
}
ftruncate($this->file, 0); // truncate file
//write something to just help debugging
fwrite( $this->file, "Unlocked\n");
fflush( $this->file );
$this->own = FALSE;
}
else
{
error_log("ExclusiveLock::unlock called on [$key] but its not acquired by caller");
}
return TRUE; // success
}
};
ファイル システムや mysql に頼ることなく 、 apc_add関数を使用してこれを実現できます。apc_add
変数がまだ保存されていない場合にのみ成功します。したがって、ロックのメカニズムを提供します。TTL を使用して、失敗したロック所有者がロックを永久に保持し続けないようにすることができます。
その理由apc_add
は、正しい解決策は、ロックのチェックと「ロック済み」への設定の間に存在する競合状態を回避するためです。まだ設定されてapc_add
いない場合にのみ値を設定する (キャッシュに「追加」する) ため、時間の近さに関係なく、一度に 2 つの呼び出しでロックを取得できないことが保証されます。ロックのチェックと設定を同時に行わないソリューションは、本質的にこの競合状態に悩まされることはありません。競合状態なしで正常にロックするには、1 つのアトミック操作が必要です。
APC ロックはその php 実行のコンテキストでのみ存在するため、ホスト間のロックをサポートしていないため、一般的なロックにはおそらく最適なソリューションではありません。 Memcache
もアトミック追加機能を提供するため、ホスト間でロックする方法の 1 つであるこの手法で使用することもできます。 Redis
また、アトミックな「SETNX」機能と TTL もサポートしており、ホスト間のロックと同期の非常に一般的な方法です。ただし、OPは特にAPCのソリューションを要求しています。
ロックの目的が、複数のプロセスが空のキャッシュ キーに値を設定しようとするのを防ぐことである場合、ブロッキング ロックを使用しない理由はありません。
$value = apc_fetch($KEY);
if ($value === FALSE) {
shm_acquire($SEMAPHORE);
$recheck_value = apc_fetch($KEY);
if ($recheck_value !== FALSE) {
$new_value = expensive_operation();
apc_store($KEY, $new_value);
$value = $new_value;
} else {
$value = $recheck_value;
}
shm_release($SEMAPHORE);
}
キャッシュが良好な場合は、そのまま使用します。キャッシュに何もない場合は、ロックを取得します。ロックを取得したら、キャッシュをダブルチェックして、ロックを取得するのを待っている間にキャッシュが再作成されていないことを確認する必要があります。キャッシュが再作成された場合は、その値を使用してロックを解放します。それ以外の場合は、計算を行い、キャッシュを作成してからロックを解放します。
実際に、これが Peter の提案よりもうまく機能するかどうかを確認してください。
排他ロックを使用し、それに慣れている場合は、ファイルをロックしようとした他のすべてを 2 ~ 3 秒のスリープ状態にします。正しく行われた場合、サイトはロックされたリソースに関するハングを経験しますが、同じものをキャッシュするために戦うスクリプトの大群は発生しません.
ロックをファイルシステムに基づいて行うことを気にしない場合は、モード 'x' で fopen() を使用できます。次に例を示します。
$f = fopen("lockFile.txt", 'x');
if($f) {
$me = getmypid();
$now = date('Y-m-d H:i:s');
fwrite($f, "Locked by $me at $now\n");
fclose($f);
doStuffInLock();
unlink("lockFile.txt"); // unlock
}
else {
echo "File is locked: " . file_get_contents("lockFile.txt");
exit;
}
www.php.net/fopen を参照
これは1年前のものだと思いますが、PHPのロックについて自分で調査しているときに、この質問に出くわしました.
APC自体を使用して解決策が可能かもしれないと思います。私を狂ったと呼んでください、しかしこれは実行可能なアプローチかもしれません:
function acquire_lock($key, $expire=60) {
if (is_locked($key)) {
return null;
}
return apc_store($key, true, $expire);
}
function release_lock($key) {
if (!is_locked($key)) {
return null;
}
return apc_delete($key);
}
function is_locked($key) {
return apc_fetch($key);
}
// example use
if (acquire_lock("foo")) {
do_something_that_requires_a_lock();
release_lock("foo");
}
実際には、既存の APC キーとの衝突を防ぐためだけに、ここで使用するキーを生成する別の関数をそこにスローすることがあります。
function key_for_lock($str) {
return md5($str."locked");
}
この$expire
パラメータは、スクリプトが終了した場合などにロックが永久に保持されるのを防ぐため、APC の使用に適した機能です。
この回答が、1年後にここでつまずいた人にとって役立つことを願っています。
これが仕事を処理する最良の方法であるかどうかはわかりませんが、少なくとも便利です。
function WhileLocked($pathname, callable $function, $proj = ' ')
{
// create a semaphore for a given pathname and optional project id
$semaphore = sem_get(ftok($pathname, $proj)); // see ftok for details
sem_acquire($semaphore);
try {
// capture result
$result = call_user_func($function);
} catch (Exception $e) {
// release lock and pass on all errors
sem_release($semaphore);
throw $e;
}
// also release lock if all is good
sem_release($semaphore);
return $result;
}
使い方はこのように簡単です。
$result = WhileLocked(__FILE__, function () use ($that) {
$this->doSomethingNonsimultaneously($that->getFoo());
});
3 番目のオプション引数は、この関数をファイルごとに複数回使用する場合に便利です。
最後に大事なことを言い忘れましたが、この関数を (署名を保持したまま) 後で別の種類のロック メカニズムを使用するように変更することは難しくありません。たとえば、複数のサーバーを使用している場合などです。
EAcceleratorにはそのためのメソッドがあります。eaccelerator_lock
とeaccelerator_unlock
。
APC は現在、保守されておらず、死んでいると見なされています。後継のAPCuは を介してロックを提供しますapcu_entry
。ただし、他の APCu 関数の同時実行も禁止されることに注意してください。ユースケースによっては、これで問題ない場合があります。
マニュアルから:
注:制御
apcu_entry()
がキャッシュのロックに入ると排他的に取得され、制御が終了すると解放されapcu_entry()
ます。実際には、これにより本体がgenerator
クリティカル セクションになり、2 つのプロセスが同じコード パスを同時に実行できなくなります。さらに、同じロックを取得するため、他の APCu 関数の同時実行を禁止します。
実際、私が見つけたのは、ロックはまったく必要ないということです...私が作成しようとしているのは、オートロードのすべてのクラス=>パスの関連付けのマップであるため、 1 つのプロセスが他のプロセスが見つけたものを上書きします (適切にコーディングされていれば、その可能性はほとんどありません)。したがって、解決策は「ロックなし」であることが判明しました。