在以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中也没有监视器(实际上会解决它),而且我不太热衷于使用一些共享内存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);
}
在上面的代码中,如果我调用doSomething1
,sem_release
内的doSomething2
是否已经释放了其他进程的信号量,或者信号量计数器实际设置为“2”(即使它只有一个容量,因为sem_get
中没有指定任何其他内容,并且信号量保持锁定直到第二次释放?
显然,我需要它保持锁定,直到doSOmething1
完成其工作。当然,我可以复制doSomething2
的内容,但这违反了DRY原则,我想避免它。当然,我还要将doSOmething2
内的工作打包到一个私有函数中并从另一个函数中调用它,但这也是额外的,基本上不必要的开销 - 所以我在做之前先做好准备。而且,当然³,真实的并不是那么简单。
我知道信号量一般如何工作,但由于存在多种实现策略,我想确保System V信号量以我期望它们工作的方式工作(即,增加计数器并且需要尽可能多的调用他们收到free
来电时,lock
。
答案 0 :(得分:0)
我自己的解决方案(目前仍在等待其他建议,所以如果你有更好/不同的解决方案,请继续!):
1)我修改了lock
和unlock
方法以计算锁定/解锁调用,并且只有在尚未锁定/已经锁定的情况下解锁时才访问信号量。
2)我在我的modify
类中添加了一个SHM
方法,该方法获取要修改的变量的键和回调。然后锁定,使用getter获取变量(因为1没有额外的锁定),调用回调并将变量传递给它,然后它使用setter(再次:没有额外的锁定)将新值写回,然后它释放了信号量。
modify
方法可以在两种模式下工作:回调可以将变量作为引用并修改它(默认),或者您可以告诉modify()
而不是分配回调的返回值到功能。这提供了最大的灵活性。
修改后的SHM类如下(仍然没有错误处理,但修改后的free
和lock
行为很好,因此可以添加):
<?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;
}
}