使用信号量在共享内存同步中嵌套锁定/空闲调用

时间:2014-02-08 12:37:13

标签: php semaphore sysv

[编辑]完成重写并添加背景(下面的原始问题)

在以PHP运行的应用程序中,出于性能原因,我使用共享内存临时存储值(数据库开销太大,文件太慢)。

我构建了一个非常简单的共享内存类,它允许脚本访问存储在共享内存中的变量,并且能够使用信号量同步调用。代码在这里(目前还没有错误处理):

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

我现在有另一个使用此共享内存类的类,需要对一个变量执行“获取,修改和写入”操作。基本上,这个:

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

现在的方式,这将锁定信号量,释放它,再次锁定它并释放它。我希望能够在整个时间内使用信号量锁定执行代码。

以前,我的代码被一个sem_acquiresem_release对包围。不幸的是,结果是(感谢@Ben),System V二进制信号量阻止了同一进程的其他lock次调用。

PHP中也没有监视器(实际上会解决它),而且我不太热衷于使用一些共享内存varialbes来自己实现它们(我想我也可以这样做......)加上传统的信号量。 我需要独占访问权限,因此非二进制信号量也不是一种选择。

在没有违反DRY原则的情况下,hwo的任何消息都会这样做吗?

原始问题

关于System V信号量如何工作以及PHP如何使用它们的简单问题:

如果我在一个进程中多次锁定(sem_acquire)一个信号量,那么每次调用时信号量值实际上都会增加(所以我需要释放它(sem_release),就像锁定它一样频繁如果进程已经拥有信号量(所以第一个sem_acquire总是解锁信号量),或者进行free的其他调用只是继续而不计数?

如果有疑问,提示如何合理地测试这个就足够了^^

示例:

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

在上面的代码中,如果我调用doSomething1sem_release内的doSomething2是否已经释放了其他进程的信号量,或者信号量计数器实际设置为“2”(即使它只有一个容量,因为sem_get中没有指定任何其他内容,并且信号量保持锁定直到第二次释放?

显然,我需要它保持锁定,直到doSOmething1完成其工作。当然,我可以复制doSomething2的内容,但这违反了DRY原则,我想避免它。当然,我还要将doSOmething2内的工作打包到一个私有函数中并从另一个函数中调用它,但这也是额外的,基本上不必要的开销 - 所以我在做之前先做好准备。而且,当然³,真实的并不是那么简单。

我知道信号量一般如何工作,但由于存在多种实现策略,我想确保System V信号量以我期望它们工作的方式工作(即,增加计数器并且需要尽可能多的调用他们收到free来电时,lock

1 个答案:

答案 0 :(得分:0)

我自己的解决方案(目前仍在等待其他建议,所以如果你有更好/不同的解决方案,请继续!):

1)我修改了lockunlock方法以计算锁定/解锁调用,并且只有在尚未锁定/已经锁定的情况下解锁时才访问信号量。

2)我在我的modify类中添加了一个SHM方法,该方法获取要修改的变量的键和回调。然后锁定,使用getter获取变量(因为1没有额外的锁定),调用回调并将变量传递给它,然后它使用setter(再次:没有额外的锁定)将新值写回,然后它释放了信号量。

modify方法可以在两种模式下工作:回调可以将变量作为引用并修改它(默认),或者您可以告诉modify()而不是分配回调的返回值到功能。这提供了最大的灵活性。

修改后的SHM类如下(仍然没有错误处理,但修改后的freelock行为很好,因此可以添加):

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

}