使用共享内存实现php"自旋锁"

此前发表过一篇关于使用信号量做php进程同步的例子:http://lajabs.net/?p=159,其主要特点就是高效简单,缺点是对信号量的管理比较复杂,在异常情况下可能未及时释放锁定造成下一次访问的死锁。
网上也有很多关于使用文件锁的,其实在高并发下性能上都很不理想。
遂考虑使用共享内存方式实现一个属于php的“自旋锁”,主要特点是:
1、检测和避免死锁
2、并可以自定义锁定超时
3、可以在运行结束后自动释放锁定
4、可搜集分析锁竞争和锁等待情况

/**
* php"自旋锁"功能 (PHP Spinlock)
* 用于php进程同步时使用
*
* Author : lajabs.net 2011/7/15
*/

class slock
{
/**
* $lock_timeout 设置等待回旋次数
* $lock_wait_func 设置等待机制,本例使用usleep+mt_rand随机等待,随机等待有利错开多个竞争
* $add_func 这里设置添加锁标志机制,本例使用PHP的APC组件apc_add函数,apc_add设定锁占有上限为5s,避免永久占有
* $del_func 这里设置删除锁标志机制,本例使用PHP的APC组件apc_delete函数
*/
private $locks;
private $lock_timeout = 200;
private $lock_wait_func;
private $add_func;
private $del_func;
public function __construct()
{
$this->add_func = function($mutex)
{
return apc_add('sl:'.$mutex,1,5);
};

$this->del_func = function($mutex)
{
return apc_delete('sl:'.$mutex);
};

$this->lock_wait_func = function()
{
usleep(mt_rand(10000,50000));
};
}

public function __destruct()
{
$this->clean();
}

/*================================= 分割线 ==========================*/

/**
* 清除当前所有设置的锁,在当前的php进程中可以设置多个锁
*/
public function clean()
{
if($this->locks)
{
foreach($this->locks as $lock => $tmp)
call_user_func($this->del_func ,$lock);
$this->locks = null;
}
}

/**
* 新建立一个锁
* 首先会判断锁定标志是否已经定义,如果已锁定则判定为死锁
* 其次使用apc共享内存方式add一个锁标志,如果失败则进入等待时间,直到超时
*/
public function lock($mutex)
{
if($this->locks[$mutex]!=null)
{
throw new Exception('Detected deadlock.');
return false;
}

while(call_user_func($this->add_func ,$mutex) == false)
{
++$i;
if($i > $this->lock_timeout)
{
throw new Exception('lock timeout.');
return false;
}
call_user_func($this->lock_wait_func);
}
$this->locks[$mutex] = 1;
return $mutex;
}

/**
* 手动释放锁,一般不用,
* 在当前对象析构时会自动释放所有锁
*/
public function release($mutex)
{
if($mutex == false) return false;
unset($this->locks[$mutex]);
call_user_func($this->del_func ,$mutex);
return true;
}
}

/*
Example:

$lock = new slock();
echo 'Start:',date('H:m:s'),',';
$lock->lock(123);
sleep(3);
echo 'Stop:',date('H:m:s');
//$lock->release(123);
*/

  

运行上述测试例子,测试同步情况:
在两个浏览器打开运行这个代码,分别输出,
浏览器1:
Start:13:07:19,Stop:13:07:22
浏览器2:
Start:13:07:19,Stop:13:07:25

第一个浏览器显示程序运行了3秒后结束。
第二个浏览器等待3秒后开始运行,总共耗时6秒,测试通过。

posted @ 2011-07-19 11:28  lajabs  阅读(1441)  评论(0编辑  收藏  举报