Bloqueo anidado/llamadas libres en sincronización de memoria compartida usando semáforos

[EDITAR] Reescritura completa con fondo agregado (pregunta original a continuación)

En una aplicación que se ejecuta en PHP, utilicé la memoria compartida para almacenar valores temporalmente por razones de rendimiento (la base de datos tiene demasiada sobrecarga y los archivos son demasiado lentos).

Creé una clase de memoria compartida realmente simple que da acceso a los scripts a las variables almacenadas en la memoria compartida y tiene la capacidad de sincronizar llamadas usando semáforos. El código está aquí (sin manejo de errores hasta el momento):

class SHM {

    private static $defaultSize = 10000;

    private static function getIdentifier ($identFile, $projId) {
        return ftok($identFile, $projId);
    }

    private $sem = NULL;
    private $shm = NULL;
    private $identFile;
    private $projId;
    private $size;

    public function __construct($identFile, $projId, $size=NULL) {
        if ($size === NULL) $size = self::$defaultSize;
        $this->identFile = $identFile;
        $this->projId    = $projId;
        $this->size      = $size;
    }

    public function __destruct() {
        if ($this->sem) {
            $this->lock();
            if ($this->shm) {
                shm_detach($this->shm);
            }
            $this->free();
        }
    }

    public function exists ($key) {
        return shm_has_var($this->getShm(), $key);
    }

    public function get ($key, $lock=true) {
        if ($this->exists ($key)) {
            if ($lock) $this->lock();
            $var = shm_get_var($this->getShm(), $key);
            if ($lock) $this->free();
            return $var;
        } else return NULL;
    }

    public function set ($key, $var, $lock=true) {
        if ($lock) $this->lock();
        shm_put_var($this->getShm(), $key, $var);
        if ($lock) $this->free();
    }

    public function remove ($key, $lock=true) {
        if ($this->exists ($key)) {
            if ($lock) $this->lock();
            $result = shm_remove_var($this->getShm(), $key);
            if ($lock) $this->free();
            return $result;
        } else return NULL;
    }

    public function clean () {
        $this->lock();
        shm_remove($this->shm);
        $this->free();
        sem_remove($this->sem);
        $this->shm = NULL;
        $this->sem = NULL;
    }

    private function getSem () {
        if ($this->sem === NULL) {
            $this->sem = sem_get(self::getIdentifier($this->identFile, $this->projId));
        }
        return $this->sem;
    }

    private function lock () {
        return sem_acquire($this->getSem());
    }

    private function free () {
        return sem_release($this->getSem());
    }

    private function getShm () {
        if ($this->shm === NULL) {
            $this->shm = shm_attach(self::getIdentifier($this->identFile, $this->projId), $this->size);
        }
        return $this->shm;
    }
}

Ahora tengo otra clase que usa esta clase de memoria compartida y necesita realizar una operación de "obtener, modificar y escribir" en una variable. Básicamente, esto:

function getModifyWrite () {
   $var = $mySHM->get('var');
   $var += 42;
   $mySHM->set('var', $var);
}

Tal como está ahora, esto bloquearía el semáforo, lo liberaría, lo bloquearía de nuevo y lo liberaría. Me encantaría tener el código ejecutado con el semáforo bloqueado todo el tiempo.

Anteriormente, tenía el código rodeado por uno sem_acquire y sem_release par. Desafortunadamente, resultó (gracias @Ben) que los semáforos binarios de System V se bloquean en lock llamadas por el mismo proceso.

No hay monitores en PHP tampoco (eso realmente lo resolvería), y no estoy muy interesado en implementarlos por mi cuenta usando algunos varialbes de memoria compartida (también supongo que podría hacer esto...) más los semáforos tradicionales . Necesito acceso exclusivo, por lo que los semáforos no binarios tampoco son una opción.

¿Alguna sugerencia sobre cómo hacer esto, sin violar los principios DRY?

pregunta original

Solo una pregunta rápida sobre cómo funcionan los semáforos de System V y cómo los usa PHP:

Si cierro (sem_acquire) un semáforo en un proceso varias veces, ¿el valor del semáforo realmente aumenta con cada llamada (por lo que necesito liberar (sem_release) tan a menudo como lo bloqueé), o hacer llamadas adicionales de sem_acquire simplemente continúa sin contar si el proceso ya posee el semáforo (por lo que el primer free siempre desbloquea el semáforo)?

En caso de duda, una pista sobre cómo probar esto razonablemente sería suficiente ^^

Ejemplo:

$sem = sem_get(ftok('/some/file', 'a'));

function doSomething1 () {
     sem_acquire($sem);
     doSomething2();
     // do something else
     sem_release($sem);
}

function doSomething2 () {
     sem_acquire($sem);
     // do stuff
     sem_release($sem);
}

En el código de arriba, si llamo doSomething1, sería el sem_release dentro doSomething2 ya liberó el semáforo para otros procesos, o el contador de semáforos estaba realmente configurado en "2" (aunque solo tiene una capacidad de uno, ya que no se especificó nada más en sem_get) y el semáforo permanece bloqueado hasta que se libera por segunda vez?

Necesito que permanezca bloqueado hasta doSOmething1 ha terminado su trabajo, obviamente. Podría, por supuesto, simplemente copiar el contenido de doSomething2, pero esto viola los principios DRY y quiero evitarlo. Podría, por supuesto, también empacar el trabajo dentro doSOmething2 dentro de una función privada y llamar a esa de las otras dos, pero eso también es una sobrecarga adicional, potencialmente innecesaria, por lo que estoy preguntando primero antes de hacerlo. Y, por supuesto ³, la cosa real no es tan simple.

SÍ sé cómo funcionan los semáforos en general, pero como existen múltiples estrategias de implementación, quiero asegurarme de que los semáforos de System V funcionen de la manera en que espero que funcionen (es decir, aumentando el contador y requiriendo tantas llamadas a freecomo recibieron lock llamadas).

preguntado el 08 de febrero de 14 a las 12:02

no quisiera doSomething2 esperar para siempre? -

@Ben: No, los semáforos se asignan a procesos/subprocesos individuales, no a funciones. El proceso que ejecuta el código es el propietario, por lo que debería tener acceso a él. No es TAN fácil producir un punto muerto;) (Por supuesto, otro hilo/proceso que se ejecuta doSOmething2 bloquearía hasta que el primer proceso libere el semáforo...) -

a tu max_acquire es 1 en sem_get por defecto, y "Un proceso que intenta adquirir un semáforo que ya ha adquirido se bloqueará para siempre si la adquisición del semáforo hace que se exceda su número máximo de semáforos". (php.net/manual/en/function.sem-acquire.php) -

@Ben: Oh, lo siento. emmm... tienes razón. ¡Maldición! -

Oh, bien... empezaste a confundirme allí por un tiempo :) -

1 Respuestas

Mi propia solución (por ahora, sigo esperando otras sugerencias, así que si tiene una solución mejor/diferente, ¡adelante!):

1) Modifiqué el locky unlock métodos para contar llamadas de bloqueo/desbloqueo y solo acceder al semáforo si aún no se ha bloqueado/se ha desbloqueado con tanta frecuencia como bloqueado.

2) Agregué un modify método a mi SHM clase que toma la clave de una variable a modificar y una devolución de llamada. Luego bloquea, obtiene la variable usando el getter (sin bloqueo adicional debido a 1), llama a la devolución de llamada y le pasa la variable, luego usa el setter (nuevamente: sin bloqueo adicional) para escribir el nuevo valor de vuelta, luego libera el semáforo.

La modify El método puede funcionar en dos modos: la devolución de llamada puede tomar la variable como referencia y modificarla (predeterminado), o puede decirle modify() para asignar en su lugar el valor de retorno de la devolución de llamada a la función. Esto proporciona la máxima flexibilidad.

La clase SHM modificada está debajo (todavía sin manejo de errores, pero modificada free y lock comportarse bien para que se pueda agregar):

<?php namespace Utilities;

 //TODO: ERROR HANDLING

class SHM {

    private static function getIdentifier ($identFile, $projId) {
        return ftok($identFile, $projId);
    }

    private static $defaultSize = 10000;

    private $sem = NULL;
    private $shm = NULL;
    private $identFile;
    private $projId;
    private $size;
    private $locked=0;

    public function __construct($identFile, $projId, $size=NULL) {
        if ($size === NULL) $size = self::$defaultSize;
        $this->identFile = $identFile;
        $this->projId    = $projId;
        $this->size      = $size;
    }

    public function __destruct() {
        if ($this->sem) {
            $this->lock();
            if ($this->shm) {
                shm_detach($this->shm);
            }
            $this->free();
        }
    }

    public function clean () {
        $this->lock();
        shm_remove($this->shm);
        $this->free();
        sem_remove($this->sem);
        $this->shm = NULL;
        $this->sem = NULL;
    }

    public function __isset($key) {
        return $this->exists($key);
    }

    public function __get($key) {
        return $this->get($key);
    }

    public function __set($key, $val) {
        return $this->set($key, $val);
    }

    public function __unset($key) {
        return $this->remove($key);
    }

    public function exists ($key) {
        return shm_has_var($this->getShm(), $key);
    }

    public function get ($key, $lock=true) {
        if ($this->exists ($key)) {
            if ($lock) $this->lock();
            $var = shm_get_var($this->getShm(), $key);
            if ($lock) $this->free();
            return $var;
        } else return NULL;
    }

    public function set ($key, $var, $lock=true) {
        if ($lock) $this->lock();
        shm_put_var($this->getShm(), $key, $var);
        if ($lock) $this->free();
    }

    public function modify ($key, $action, $useReturn = false) {
        $var = $this->get($key);
        $result = $action($var);
        if ($useReturn) {
            $var = $result;
        }
        $this->set($key, $var);
    }

    public function remove ($key, $lock=true) {
        if ($this->exists ($key)) {
            if ($lock) $this->lock();
            $result = shm_remove_var($this->getShm(), $key);
            if ($lock) $this->free();
            return $result;
        } else return NULL;
    }

    private function getSem () {
        if ($this->sem === NULL) {
            $this->sem = sem_get(self::getIdentifier($this->identFile, $this->projId));
        }
        return $this->sem;
    }

    private function getShm () {
        if ($this->shm === NULL) {
            $this->shm = shm_attach(self::getIdentifier($this->identFile, $this->projId), $this->size);
        }
        return $this->shm;
    }

    private function lock () {
        if ($this->locked == 0) {
            $result = sem_acquire($this->getSem());
            if (!$result) return 0;
        }
        return ++$this->locked;
    }

    private function free () {
        if ($this->locked == 1) {
            $result = sem_release($this->getSem());
            if (!$result) return 0;
        }
        return --$this->locked;
    }

}

Respondido 08 Feb 14, 14:02

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.