[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 free
as they received lock
calls).