Question

[EDIT] Complete rewrite with added Background (original question below)

In an application running in PHP I used shared memory to store values temporarily for performance reasons (database has too much overhead and files are too slow).

I built a really simple shared memory class that gives scripts access to variables stored in shared memory and has the ability to synchronize calls using semaphores. The code is here (no error handling as of yet):

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

I now have another class that uses this shared memory class and needs to perform a "get, modify and write" operation on one variable. Basically, this:

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

The way it is now, this would lock the semaphore, free it, lock it again, and free it. I would love to have the code executed with the semaphore locke dthe whole time.

Previously, I had the code surrounded by one sem_acquire and sem_release pair. Unfortunately it turned out (thanks @Ben) that System V binary semaphores block on additional lock calls by the same process.

There are no monitors in PHP either (that would actually solve it), and I'm not too keen on implementing them on my own using some shared-memory varialbes (also I guess I could do this...) plus the traditional semaphores. I need exclusive access, so non-binary semaphores aren't an option either.

Any sugestions on hwo to do this, without violating DRY principles?

original question

Just a quick question on how System V semaphores work and how PHP uses them:

If I lock (sem_acquire) one semaphore in one process multiple times, is the semaphore value actually increased with every call (so I need to free (sem_release) it as often as I locked it), or do additional calls of sem_acquire just continues without counting up if the process already owns the semaphore (so the first free always unlocks the semaphore)?

When in doubt, a hint on how to reasonably test this would be enough ^^

Example:

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

In the code above, If I call doSomething1, would the sem_release inside doSomething2 already free the semaphore for other processes, or was the semaphore counter actually set to "2" (even though it only has a capacity of one, as nothing else was specified in sem_get) and the semaphore stays locked until released the second time?

I need it to stay locked until doSOmething1 has finished its work, obviously. I could, of course, ujst copy the contets of doSomething2, but this violates DRY principles and I want to avoid it. I coulkd, of course, also pack the work inside doSOmething2 inside a private function and call that one from both others, but that is additional, potntially unnecessary overhead, too - so I'm aksing first before doing it. And, of course ³, the real thing isn't that simple.

I DO know how semaphores in general work, but as there are multiple implementation strategies, I want to make sure System V semaphores work the way I'd expect them to work (that is, increasing the counter and requireng as many calls to freeas they received lock calls).

Était-ce utile?

La solution

My own solution (for now - still waiting for other suggestions, so if you got a better/different soltution, go ahead!):

1) I modified the lockand unlock methods to count lock/unlock calls and only access the semaphore if it hasn't been locked yet / has been unlocked as often as locked.

2) I added a modify method to my SHM class that takes the key of a variable to modify and a callback. It then locks, gets the variable using the getter (no additional locking because of 1) ), calls the callback and passes the variable to it, afterwards it uses the setter (again: no additional locking) to write the new value back, then it frees the semaphore.

The modify method can work in two modes: either the callback can take the variable as reference and modify it (default), or you can tell modify() to instead assign the return value of the callback to the function. This provides maximum flexibility.

The modified SHM class is below (still no error-handling, but modified free and lock behave nicely so it can be added):

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

}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top