meilleure façon d'obtenir un verrou en php
j'essaie de mettre à jour une variable dans APC, et il y aura de nombreux processus qui tenteront de le faire.
APC ne fournit pas de fonctionnalité de verrouillage, donc j'envisage d'utiliser d'autres mécanismes... ce que j'ai trouvé jusqu'à présent est get_lock () de mysql, et flock () de php. Autre chose vaut la peine d'examiner?
mise à jour: j'ai trouvé sem_acquire, mais il semble être un verrouillage.
10 réponses
/*
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
}
};
vous pouvez utiliser la fonction apc_add pour réaliser ceci sans avoir recours à des systèmes de fichiers ou mysql. apc_add
ne réussit que lorsque la variable n'est pas déjà stockée, fournissant ainsi un mécanisme de verrouillage. TTL peut être utilisé pour s'assurer que les serruriers falied ne garderont pas la serrure indéfiniment.
la raison pour laquelle apc_add
est la solution correcte est qu'elle évite la condition de course qui existerait autrement entre la vérification de la verrouillage et réglage à "verrouillé par vous". Comme apc_add
ne définit la valeur que si elle n'est pas déjà ("ajoute" la valeur au cache ), cela garantit que le verrou ne peut pas être acquis par deux appels à la fois, quelle que soit leur proximité dans le temps. Aucune solution qui ne coche pas et régler la serrure en même temps souffrira de façon inhérente de cette condition de course; une opération atomique est nécessaire pour verrouiller avec succès sans condition de course.
étant donné que les verrous APC n'existeront que dans le contexte de cette exécution php, ce n'est probablement pas la meilleure solution pour le verrouillage général, car il ne supporte pas les verrous entre hôtes. Memcache
fournit également une fonction d'addition atomique et peut donc également être utilisé avec cette technique - qui est une méthode de verrouillage entre les hôtes. Redis
supporte également les fonctions atomic 'SETNX' et TTL, et est une méthode très courante de verrouillage et de synchronisation entre les hôtes. Toutefois, le panel OP demandes une solution pour APC en particulier.
si le but de la serrure est d'empêcher plusieurs processus d'essayer de remplir une clé de cache vide, pourquoi ne voudriez-vous pas avoir une serrure de blocage?
$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);
}
si le cache est bon, il suffit de rouler avec. S'il n'y a rien dans la cache, vous avez une serrure. Une fois que vous avez la serrure, vous devrez revérifier le cache pour vous assurer que, pendant que vous attendiez pour obtenir la serrure, le cache n'a pas été repeuplé. Si le cache a été repeuplé, utilisez cette valeur & relâchez le verrou, sinon, vous faites le calcul, peuplez le cache et relâchez votre verrou.
en fait, vérifiez si cela fonctionnera mieux que la suggestion de Peter.
utilisez une serrure exclusive et si vous êtes à l'aise avec elle, mettez tout le reste qui a tenté de verrouiller le fichier dans un sommeil de 2-3 secondes. Si cela est fait correctement, votre site connaîtra un hang en ce qui concerne la ressource verrouillée, mais pas une horde de scripts luttant pour mettre en cache le samething.
si cela ne vous dérange pas de baser votre lock sur le système de fichiers, alors vous pouvez utiliser fopen() avec le mode 'x'. Voici un exemple:
$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: " . get_file_contents("lockFile.txt");
exit;
}
voir www.php.net/fopen
je me rends compte que c'est vieux d'un an, mais j'ai juste trébuché sur la question en faisant quelques recherches moi-même sur le verrouillage en PHP.
il me semble qu'une solution pourrait être possible en utilisant APC lui-même. Appelez-moi fou, mais cela pourrait être une approche réalisable:
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");
}
dans la pratique, je pourrais jeter une autre fonction là-dedans pour générer une clé à utiliser ici, juste pour prévenir la collision avec une clé APC existante, par exemple:
function key_for_lock($str) {
return md5($str."locked");
}
le paramètre $expire
est une fonctionnalité agréable D'APC à utiliser, car il empêche votre verrou d'être maintenu à jamais si votre script meurt ou quelque chose comme ça.
espérons que cette réponse soit utile à tous ceux qui trébuchent ici un an plus tard.
Ce que j'ai trouvé, en fait, c'est que je n'ai pas besoin de verrouillage... étant donné que ce que j'essaie de créer est une carte de toutes les associations de chemins de classe => pour autoload, cela n'a pas d'importance si un processus écrase ce que l'autre a trouvé (c'est hautement improbable, si codé correctement), parce que les données y parviendront finalement de toute façon. Donc, la solution s'est avérée être "pas de serrures".
EAccelerator a des méthodes pour elle; eaccelerator_lock
et eaccelerator_unlock
.
ne Peut pas dire si c'est la meilleure façon de gérer le travail, mais au moins c'est pratique.
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;
}
L'utilisation de est aussi simple que cela.
$result = WhileLocked(__FILE__, function () use ($that) {
$this->doSomethingNonsimultaneously($that->getFoo());
});
troisième argument optionnel peut devenir pratique si vous utilisez cette fonction plus d'une fois par fichier.
enfin et surtout, il n'est pas difficile de modifier cette fonction (tout en conservant sa signature) pour utiliser tout autre type de mécanisme de verrouillage à une date ultérieure, par exemple si vous vous trouvez à trouvez - vous à travailler avec plusieurs serveurs.
APC est maintenant considéré comme unmaintained and dead . Son successeur APCu offre le verrouillage via apcu_entry
. Mais sachez que cela interdit également l'exécution simultanée de toute autre fonction de L'APCu. Selon votre cas d'utilisation, cela pourrait être OK pour vous.
du manuel:
Note: lorsque la commande entre
apcu_entry()
le le verrouillage pour le cache est acquis exclusivement, il est libéré lorsque le contrôle quitteapcu_entry()
: en effet, cela transforme le corps degenerator
en une section critique, empêchant deux processus d'exécuter simultanément les mêmes chemins de code. En outre, il interdit l'exécution simultanée de toute autre fonction APCu, car ils vont acquérir la même serrure.