2008-11-28 15 views
17

Estoy tratando de actualizar una variable en APC, y habrá muchos procesos tratando de hacer eso.mejor manera de obtener un bloqueo en php

APC no proporciona funcionalidad de bloqueo, por lo que estoy considerando el uso de otros mecanismos ... lo que he encontrado hasta ahora es GET_LOCK() de mysql, y el rebaño de php(). ¿Algo más que valga la pena considerar?

Actualización: He encontrado sem_acquire, pero parece ser un bloqueo de bloqueo.

+0

Qué contiene la variable, exactamente; ¿Por qué estás preocupado por el bloqueo? Es posible que pueda evitar el problema. – Rob

+1

Una (última) palabra de advertencia: MySQL GET_LOCK() tiene un comportamiento muy peligroso.Un segundo GET_LOCK() libera silenciosamente el antiguo bloqueo en la misma conexión. MySQL solo puede contener UN bloqueo por conexión. Los bloqueos anidados son imposibles con stock MySQL. No debe usarse para bloqueo de uso general. – korkman

Respuesta

2

Si no le importa basar su bloqueo en el sistema de archivos, entonces podría usar fopen() con el modo 'x'. Aquí está un ejemplo:

$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; 
} 

Ver www.php.net/fopen

+0

Siempre que nunca necesite NFS, esta es probablemente la solución más fácil. Aunque hay una buena posibilidad de obtener una condición de carrera o peor, un montón si el script de bloqueo se bloquea antes de liberar al grupo. – David

+1

Sí, puede acumularse si falla el script, pero hay formas de evitarlo, o al menos detectar el problema utilizando el PID y el tiempo escrito dentro del archivo de bloqueo y enviar un correo electrónico. –

3

En realidad, comprobar para ver si esto va a funcionar mejor que la sugerencia de Pedro.

http://us2.php.net/flock

usar un bloqueo exclusivo y si su gusto con ella, poner todo lo que intentó bloquear el archivo en un segundo sueño 2-3. Si lo hace bien, su sitio experimentará un bloqueo con respecto al recurso bloqueado, pero no una horda de scripts que luchan por almacenar en caché lo mismo.

4

Si el objetivo del bloqueo es evitar que varios procesos intenten llenar una clave de caché vacía, ¿por qué no desea tener un bloqueo de bloqueo?


    $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 la memoria caché es buena, simplemente rueda con ella. Si no hay nada en la memoria caché, obtienes un bloqueo. Una vez que tenga el bloqueo, tendrá que volver a verificar el caché para asegurarse de que, mientras esperaba el bloqueo, el caché no se volvió a llenar. Si se volvió a llenar el caché, use ese valor & libere el bloqueo; de lo contrario, realice el cálculo, llene el caché & y luego suelte el bloqueo.

+1

El motivo por el que no se utiliza un bloqueo de bloqueo es que, dado que habrá muchos de esos procesos, los ralentizará significativamente. Prefiero que no actualicen la variable que esperar y causar un colapso a medida que se acumulan. – tpk

0

Lo que he encontrado, en realidad, es que no necesito ningún bloqueo en absoluto ... dado que lo que estoy tratando de crear es un mapa de todas las asociaciones class => path para autocarga, no lo hace No importa si un proceso sobrescribe lo que el otro ha encontrado (es muy poco probable, si está codificado correctamente), porque de todos modos los datos llegarán eventualmente. Entonces, la solución resultó ser "sin bloqueos".

+4

Si ese es el caso, debe cerrar la pregunta. – Powerlord

1

Me doy cuenta de que tiene un año de antigüedad, pero acabo de tropezar con la pregunta mientras investigaba sobre bloquear PHP.

Se me ocurre que una solución podría ser posible utilizando el propio APC. Llámame loco, pero esto podría ser un enfoque viable:

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"); 
} 

En la práctica podría lanzar otra función allí para generar una clave para usar aquí, sólo para evitar la colisión con una clave de APC existente, por ejemplo:

function key_for_lock($str) { 
    return md5($str."locked"); 
} 

El parámetro $expire es una buena característica de APC a utilizar, ya que evita el bloqueo del detenido para siempre si su guión muere o algo por el estilo.

Esperemos que esta respuesta sea útil para cualquier otra persona que tropiece aquí un año después.

+4

Dado que 'aquire_lock' no es atómico, no es realmente útil cuando necesita un bloqueo debido a los accesos concurrentes a algún recurso. – cweiske

+2

Solución no segura para subprocesos – zerkms

+1

Si el script muere, ¿lo liberará APC? –

13
/* 
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 
    } 
}; 
+3

__destruct no se llamará en caso de error fatal, ¿verdad? Me preocupan las situaciones que podrían provocar que el bloqueo se atasque permanentemente y requiera intervención manual. – MeatPopsicle

+1

'flock's se borran cuando se cierra un archivo, y los archivos se cierran cuando se cierra el proceso de php. Entonces no debería ser un problema. – Sam

+0

llamar a 'flock' y bloquear un archivo en el disco es mucho más caro que la operación de APC real, por lo tanto, es una forma muy costosa de coordinar el caché de APC. –

7

Puede usar la función apc_add para lograr esto sin tener que recurrir a los sistemas de archivos o a mysql. apc_add solo tiene éxito cuando la variable no está almacenada; por lo tanto, proporcionando un mecanismo de bloqueo. El TTL se puede utilizar para garantizar que los portadores de portas fallados no sigan sujetando el candado para siempre.

El motivo apc_add es la solución correcta porque evita la condición de carrera que, de lo contrario, existiría entre la comprobación del bloqueo y la configuración en "bloqueado por usted". Dado que apc_add solo establece el valor si no es ya establecido ("lo agrega" a la memoria caché), garantiza que el bloqueo no puede ser adquirido por dos llamadas a la vez, independientemente de su proximidad en el tiempo. Ninguna solución que no marque y establecer el bloqueo al mismo tiempo sufrirá inherentemente esta condición de carrera; se requiere una operación atómica para bloquear con éxito sin condiciones de carrera.

Dado que los bloqueos de APC solo existirán en el contexto de esa ejecución de php, probablemente no sea la mejor solución para el bloqueo general, ya que no admite bloqueos entre hosts. Memcache también proporciona una función de adición atómica y, por lo tanto, también se puede utilizar con esta técnica, que es un método de bloqueo entre hosts. Redis también es compatible con funciones atómicas 'SETNX' y TTL, y es un método muy común de bloqueo y sincronización entre hosts. Howerver, el OP solicita una solución para APC en particular.

+1

¿Cómo se libera el "bloqueo" si el proceso que creó la variable original muere sin eliminarlo? Supongo que no. La liberación automática de bloqueos en caso de falla es una característica importante de los bloqueos. – Jason

+0

gran pregunta @ Jason, ampliaré la respuesta. –

0

No puedo decir si esta es la mejor manera de manejar el trabajo, pero al menos es conveniente.

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; 
} 

El uso es así de simple.

$result = WhileLocked(__FILE__, function() use ($that) { 
    $this->doSomethingNonsimultaneously($that->getFoo()); 
}); 

El tercer argumento opcional puede ser útil si usa esta función más de una vez por archivo.

Por último pero no menos importante, no es difícil modificar esta función (manteniendo su firma) para utilizar cualquier otro tipo de mecanismo de bloqueo en una fecha posterior, p. si te encuentras trabajando con varios servidores.

0

APC ahora se considera unmaintained and dead. Su sucesor APCu ofrece bloqueo a través de apcu_entry. Pero tenga en cuenta que también prohíbe la ejecución concurrente de cualquier otra función APCu. Dependiendo de su caso de uso, esto podría estar bien para usted.

Del manual:

Nota: Cuando el control entra apcu_entry() la cerradura de la memoria caché se adquiere en exclusiva, que se libera cuando el control deja apcu_entry(): En efecto, esto convierte el cuerpo de generator en una sección crítica, impidiendo que dos procesos ejecuten las mismas rutas de código al mismo tiempo. Además, prohíbe la ejecución concurrente de cualquier otra función APCu, ya que adquirirá el mismo bloqueo.

Cuestiones relacionadas