php并发加锁问题分析与设计代码实例讲解
在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误。下面我将分析一个财务支付锁的问题。希望对大家有所帮助。
1没有应用锁机制
1.1财务支付简化版本代码
$total)
{
returnfalse;
}
//余额
$left=$total-$money;
//更新余额
returnsetUserLeftMoney($userId,$left);
}
//取出用户的余额
functiongetUserLeftMoney($userId)
{
if(false==is_int($userId))
{
return0;
}
$sql="selectaccountformuser_accountwhereuserid=${userId}";
//$mysql=newmysql();//mysql数据库
return$mysql->query($sql);
}
//更新用户余额
functionsetUserLeftMoney($userId,$money)
{
if(false==is_int($userId)||false==is_int($money))
{
returnfalse;
}
$sql="updateuser_accountsetaccount=${money}whereuserid=${userId}";
//$mysql=newmysql();//mysql数据库
return$mysql->execute($sql);
}
?>
1.2问题分析
如果有两个操作人(p和m),都用用户编号100账户,分别在pc和手机端同时登陆,100账户总余额有1000,p操作人花200,m操作人花300。并发过程如下。
p操作人:
取出用户的余额1000。
支付后剩余800=1000-200。
更新后账户余额800。
m操作人:
取出用户余额1000。
支付后剩余700=1000-300。
支付后账户余额700。
两次支付后,账户的余额居然还有700,应该的情况是花费了500,账户余额500才对。造成这个现象的根本原因,是并发的时候,p和m同时操作取到的余额数据都是1000。
2加锁设计
锁的操作一般只有两步,一获取锁(getLock);二是释放锁(releaseLock)。但现实锁的方式有很多种,可以是文件方式实现;sql实现;Memcache实现;根据这种场景我们考虑使用策略模式。
createLock($type,$options);
}
}
publicfunctioncreateLock($type,$options=array())
{
if(false==in_array($type,self::$_supportLocks))
{
thrownewException("notsupportlockof${type}");
}
$this->_lock=new$type($options);
}
publicfunctiongetLock($key,$timeout=ILock::EXPIRE)
{
if(false==$this->_lockinstanceofILock)
{
thrownewException('false==$this->_lockinstanceofILock');
}
$this->_lock->getLock($key,$timeout);
}
publicfunctionreleaseLock($key)
{
if(false==$this->_lockinstanceofILock)
{
thrownewException('false==$this->_lockinstanceofILock');
}
$this->_lock->releaseLock($key);
}
}
interfaceILock
{
constEXPIRE=5;
publicfunctiongetLock($key,$timeout=self::EXPIRE);
publicfunctionreleaseLock($key);
}
classFileLockimplementsILock
{
private$_fp;
private$_single;
publicfunction__construct($options)
{
if(isset($options['path'])&&is_dir($options['path']))
{
$this->_lockPath=$options['path'].'/';
}
else
{
$this->_lockPath='/tmp/';
}
$this->_single=isset($options['single'])?$options['single']:false;
}
publicfunctiongetLock($key,$timeout=self::EXPIRE)
{
$startTime=Timer::getTimeStamp();
$file=md5(__FILE__.$key);
$this->fp=fopen($this->_lockPath.$file.'.lock',"w+");
if(true||$this->_single)
{
$op=LOCK_EX+LOCK_NB;
}
else
{
$op=LOCK_EX;
}
if(false==flock($this->fp,$op,$a))
{
thrownewException('failed');
}
returntrue;
}
publicfunctionreleaseLock($key)
{
flock($this->fp,LOCK_UN);
fclose($this->fp);
}
}
classSQLLockimplementsILock
{
publicfunction__construct($options)
{
$this->_db=newmysql();
}
publicfunctiongetLock($key,$timeout=self::EXPIRE)
{
$sql="SELECTGET_LOCK('".$key."','".$timeout."')";
$res=$this->_db->query($sql);
return$res;
}
publicfunctionreleaseLock($key)
{
$sql="SELECTRELEASE_LOCK('".$key."')";
return$this->_db->query($sql);
}
}
classMemcacheLockimplementsILock
{
publicfunction__construct($options)
{
$this->memcache=newMemcache();
}
publicfunctiongetLock($key,$timeout=self::EXPIRE)
{
$waitime=20000;
$totalWaitime=0;
$time=$timeout*1000000;
while($totalWaitime<$time&&false==$this->memcache->add($key,1,$timeout))
{
usleep($waitime);
$totalWaitime+=$waitime;
}
if($totalWaitime>=$time)
thrownewException('cannotgetlockforwaiting'.$timeout.'s.');
}
publicfunctionreleaseLock($key)
{
$this->memcache->delete($key);
}
}
3应用锁机制
3.1支付系统应用锁
getLock($lockKey,8);
//取出总额
$total=getUserLeftMoney($userId);
//花费大于剩余
if($money>$total)
{
$ret=false;
}
else
{
//余额
$left=$total-$money;
//更新余额
$ret=setUserLeftMoney($userId,$left);
}
//释放锁
$lockSystem->releaseLock($lockKey);
}
catch(Exception$e)
{
//释放锁
$lockSystem->releaseLock($lockKey);
}
}
//取出用户的余额
functiongetUserLeftMoney($userId)
{
if(false==is_int($userId))
{
return0;
}
$sql="selectaccountformuser_accountwhereuserid=${userId}";
//$mysql=newmysql();//mysql数据库
return$mysql->query($sql);
}
//更新用户余额
functionsetUserLeftMoney($userId,$money)
{
if(false==is_int($userId)||false==is_int($money))
{
returnfalse;
}
$sql="updateuser_accountsetaccount=${money}whereuserid=${userId}";
//$mysql=newmysql();//mysql数据库
return$mysql->execute($sql);
}
?>
3.2锁分析
p操作人:
获取锁:pay100
取出用户的余额1000。
支付后剩余800=1000-200。
更新后账户余额800。
释放锁:pay100
m操作人:
1、等待锁:pay100
2、获取锁:pay100
3、获取余额:800
3、支付后剩余500=800-300。
5、支付后账户余额500。
6、释放锁:pay100
两次支付后,余额500。非常完美了解决了并发造成的临界区资源的访问问题。
到此这篇关于php并发加锁问题分析与设计代码实例讲解的文章就介绍到这了,更多相关php并发加锁问题分析与设计内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。