with - La mejor forma de obtener un bloqueo en php




strip_tags() (7)

Estoy tratando de actualizar una variable en APC, y muchos procesos intentarán hacerlo.

APC no proporciona funcionalidad de bloqueo, por lo que estoy considerando utilizar 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.


Si no le importa basar su bloqueo en el sistema de archivos, entonces podría usar fopen () con el modo 'x'. Aquí hay 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;
}

Vea www.php.net/fopen


En realidad, verifique si esto funciona mejor que la sugerencia de Peter.

http://us2.php.net/flock

use un candado exclusivo y si se siente cómodo con él, coloque todo lo demás que intente bloquear el archivo en un período de 2 a 2 segundos. 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.


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 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".


Me doy cuenta de que tiene un año, pero me encontré con la pregunta mientras investigaba sobre cómo bloquear PHP.

Se me ocurre que una solución podría ser posible usando APC. Llámame loco, pero este 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í, solo para evitar la colisión con una clave APC existente, por ejemplo:

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

El parámetro $expire es una buena característica de APC para usar, ya que evita que su bloqueo se mantenga para siempre si su secuencia de comandos muere o algo así.

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


Puede usar la función apc_add para lograr esto sin 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.

La razón por la cual apc_add es la solución correcta es porque evita la condición de carrera que, de otro modo, existiría entre verificar el bloqueo y establecerlo en "bloqueado por usted". Dado que apc_add solo establece el valor si no está ya configurado ("lo agrega" a la memoria caché), garantiza que dos llamadas a la vez no puedan realizar el bloqueo, independientemente de su proximidad en el tiempo. Ninguna solución que no verifique y establezca 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.

Como 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 usar con esta técnica, que es un método de bloqueo entre hosts. Redis también es compatible con las 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.


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 tan simple como esto.

$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, por ejemplo, si te encuentras trabajando con varios servidores.


/*
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