3

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

在 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_acquire对包围sem_release。不幸的是(感谢@Ben),System V 二进制信号量阻塞lock了同一进程的其他调用。

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

在不违反 DRY 原则的情况下,对如何执行此操作有任何建议吗?

原始问题

只是一个关于 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电话)。

4

1 回答 1

0

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

1)我修改了lockandunlock方法来计算锁定/解锁调用,并且仅在信号量尚未被锁定/已被锁定时才访问信号量。

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

}
于 2014-02-08T14:02:04.030 回答