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
}
};
apc_add 関数を使用すると、ファイルシステムやmysqlに頼らずにこれを実現できます。 apc_add
変数がまだ格納されていない場合にのみ成功します。したがって、ロックのメカニズムを提供します。 TTLを使用して、失敗したロック所有者がロックを永久に保持し続けないようにすることができます。
理由 apc_add
が正しい解決策であるのは、ロックをチェックしてから「ロックされた」に設定するまでの間に存在する競合状態を回避するためです。 apc_add
値を設定するのは、alreadyが設定されていない(キャッシュに「追加」される)場合のみであり、ロックを取得できないようにします。時間の近さに関係なく、一度に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は現在 維持されておらず死んでいる と見なされます。後継です APC は apcu_entry
を介してロックを提供します。ただし、他のAPCu関数の同時実行も禁止されていることに注意してください。ユースケースによっては、これで問題ない場合があります。
マニュアルから:
注:制御が
apcu_entry()
に入ると、キャッシュのロックが排他的に取得され、制御がapcu_entry()
を離れると解放されます。 :事実上、これはgenerator
の本体をクリティカルセクションに変え、2つのプロセスが同じコードパスを同時に実行することを禁止します。さらに、他のAPCu関数は同じロックを取得するため、それらの関数を同時に実行することは禁止されています。