php并发加锁示例


Posted in PHP onOctober 17, 2016

在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误。下面我将分析一个财务支付锁的问题。希望对大家有所帮助。

1 没有应用锁机制

1.1 财务支付简化版本代码

<!--?php 
/** 
 * pay.php 
 * 
 * 支付没有应用锁
 * 
 * Copy right (c) 2016 
 * 
 * modification history: 
 * -------------------- 
 * 2016/9/10, by CleverCode, Create 
 * 
 */ 
//用户支付
function pay($userId,$money)
{
  
 if(false == is_int($userId) || false == is_int($money))
 {
  return false;
 } 
  
 //取出总额
 $total = getUserLeftMoney($userId);
  
 //花费大于剩余
 if($money --> $total)
 {
  return false; 
 }
  
 //余额
 $left = $total - $money;
  
 //更新余额
 return setUserLeftMoney($userId,$left);
 
}
 
//取出用户的余额
function getUserLeftMoney($userId)
{
 if(false == is_int($userId))
 {
  return 0;
 }
 $sql = "select account form user_account where userid = ${userId}";
  
 //$mysql = new mysql();//mysql数据库
 return $mysql->query($sql);
}
 
//更新用户余额
function setUserLeftMoney($userId,$money)
{
 if(false == is_int($userId) || false == is_int($money))
 {
  return false;
 }  
  
 $sql = "update user_account set account = ${money} where userid = ${userId}";
  
 //$mysql = new mysql();//mysql数据库
 return $mysql->execute($sql);
}
?>

1.2 问题分析

如果有两个操作人(p和m),都用用户编号100账户,分别在pc和手机端同时登陆,100账户总余额有1000,p操作人花200,m操作人花300。并发过程如下。

p操作人:

  1. 取出用户的余额1000。
  2. 支付后剩余 800 = 1000 - 200。
  3. 更新后账户余额800。

m操作人:

  1. 取出用户余额1000。
  2. 支付后剩余700 = 1000 - 300。
  3. 支付后账户余额700。

两次支付后,账户的余额居然还有700,应该的情况是花费了500,账户余额500才对。造成这个现象的根本原因,是并发的时候,p和m同时操作取到的余额数据都是1000。

2 加锁设计

锁的操作一般只有两步,一 获取锁(getLock);二是释放锁(releaseLock)。但现实锁的方式有很多种,可以是文件方式实现;sql实现;Memcache实现;根据这种场景我们考虑使用策略模式。

2.1 类图设计如下

php并发加锁示例

2.2 php源码设计如下

LockSystem.php

<!--?php 
/** 
 * LockSystem.php 
 * 
 * php锁机制
 * 
 * Copy right (c) 2016
 * 
 * modification history: 
 * -------------------- 
 * 2016/9/10, by CleverCode, Create 
 * 
 */ 
class LockSystem
{
 const LOCK_TYPE_DB = 'SQLLock';
 const LOCK_TYPE_FILE = 'FileLock';
 const LOCK_TYPE_MEMCACHE = 'MemcacheLock';
  
 private $_lock = null;
 private static $_supportLocks = array('FileLock', 'SQLLock', 'MemcacheLock'); 
  
 public function __construct($type, $options = array()) 
 {
  if(false == empty($type))
  {
   $this--->createLock($type, $options);
  }
 } 
 public function createLock($type, $options=array())
 {
  if (false == in_array($type, self::$_supportLocks))
  {
   throw new Exception("not support lock of ${type}");
  }
  $this->_lock = new $type($options);
 }  
 public function getLock($key, $timeout = ILock::EXPIRE)
 {
  if (false == $this->_lock instanceof ILock) 
  {
   throw new Exception('false == $this->_lock instanceof ILock');   
  } 
  $this->_lock->getLock($key, $timeout); 
 }
  
 public function releaseLock($key)
 {
  if (false == $this->_lock instanceof ILock) 
  {
   throw new Exception('false == $this->_lock instanceof ILock');   
  } 
  $this->_lock->releaseLock($key);   
 } 
}
interface ILock
{
 const EXPIRE = 5;
 public function getLock($key, $timeout=self::EXPIRE);
 public function releaseLock($key);
}
 
class FileLock implements ILock
{
 private $_fp;
 private $_single;
 
 public function __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;
 }
 public function getLock($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))
  {
   throw new Exception('failed');
  }
  
  return true;
 }
 public function releaseLock($key)
 {
  flock($this->fp, LOCK_UN);
  fclose($this->fp);
 }
}
 
class SQLLock implements ILock
{
 public function __construct($options)
 {
  $this->_db = new mysql(); 
 }
 
 public function getLock($key, $timeout=self::EXPIRE)
 {  
  $sql = "SELECT GET_LOCK('".$key."', '".$timeout."')";
  $res = $this->_db->query($sql);
  return $res;
 }
 
 public function releaseLock($key)
 {
  $sql = "SELECT RELEASE_LOCK('".$key."')";
  return $this->_db->query($sql);
 }
}
 
class MemcacheLock implements ILock
{
 public function __construct($options)
 {
   
  $this->memcache = new Memcache();
 }
 
 public function getLock($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)
   throw new Exception('can not get lock for waiting '.$timeout.'s.');
 
 }
 
 public function releaseLock($key)
 {
  $this->memcache->delete($key);
 }
}

3 应用锁机制

3.1 支付系统应用锁

<!--?php
 
/** 
 * pay.php 
 * 
 * 支付应用锁
 * 
 * Copy right (c) 2016 
 * 
 * modification history: 
 * -------------------- 
 * 2016/9/10, by CleverCode, Create 
 * 
 */ 
//用户支付
function pay($userId,$money)
{
  
 if(false == is_int($userId) || false == is_int($money))
 {
  return false;
 } 
  
 try
 {
  //创建锁(推荐使用MemcacheLock)
  $lockSystem = new LockSystem(LockSystem::LOCK_TYPE_MEMCACHE);    
   
  //获取锁
  $lockKey = 'pay'.$userId;
  $lockSystem--->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);  
 }
 
}
//取出用户的余额
function getUserLeftMoney($userId)
{
 if(false == is_int($userId))
 {
  return 0;
 }
 $sql = "select account form user_account where userid = ${userId}";
  
 //$mysql = new mysql();//mysql数据库
 return $mysql->query($sql);
}
 
//更新用户余额
function setUserLeftMoney($userId,$money)
{
 if(false == is_int($userId) || false == is_int($money))
 {
  return false;
 }  
  
 $sql = "update user_account set account = ${money} where userid = ${userId}";
  
 //$mysql = new mysql();//mysql数据库
 return $mysql->execute($sql);
}
?>

3.2 锁分析

p操作人:

  1.  获取锁:pay100
  2.  取出用户的余额1000。
  3.  支付后剩余 800 = 1000 - 200。
  4.  更新后账户余额800。
  5.  释放锁:pay100

 m操作人:

  1. 等待锁:pay100
  2. 获取锁:pay100
  3. 获取余额:800
  4. 支付后剩余500 = 800 - 300。
  5. 支付后账户余额500。
  6. 释放锁:pay100

两次支付后,余额500。非常完美了解决了并发造成的临界区资源的访问问题。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

PHP 相关文章推荐
swfupload 多文件上传实现代码
Aug 27 PHP
php adodb操作mysql数据库
Mar 19 PHP
将数组写入txt文件 var_export
Apr 21 PHP
用PHP查询搜索引擎排名位置的代码
Jan 05 PHP
一个基于PDO的数据库操作类
Mar 24 PHP
PHP性能优化 产生高度优化代码
Jul 22 PHP
php截取中文字符串不乱码的方法
Dec 25 PHP
PHP计算加权平均数的方法
Jul 16 PHP
微信公众号模板消息群发php代码示例
Dec 29 PHP
PHP验证码类ValidateCode解析
Jan 07 PHP
Ajax中的JSON格式与php传输过程全面解析
Nov 14 PHP
设定php简写功能的方法
Nov 28 PHP
thinkPHP自动验证、自动添加及表单错误问题分析
Oct 17 #PHP
PHP CURL post数据报错 failed creating formpost data
Oct 16 #PHP
PHPStrom 新建FTP项目以及在线操作教程
Oct 16 #PHP
php根据数据id自动生成编号的实现方法
Oct 16 #PHP
PHP结合Ueditor并修改图片上传路径
Oct 16 #PHP
jquery+thinkphp实现跨域抓取数据的方法
Oct 15 #PHP
php组合排序简单实现方法
Oct 15 #PHP
You might like
php下通过伪造http头破解防盗链的代码
2010/07/03 PHP
PHP curl模拟浏览器采集阿里巴巴的实现代码
2011/04/20 PHP
PHP中使用localhost连接Mysql不成功的解决方法
2014/08/20 PHP
php中ob_get_length缓冲与获取缓冲长度实例
2014/11/20 PHP
php实现的pdo公共类定义与用法示例
2017/07/19 PHP
PHP5中使用mysqli的prepare操作数据库的介绍
2019/03/18 PHP
js实现飞入星星特效代码
2014/10/17 Javascript
如何利用Promises编写更优雅的JavaScript代码
2016/05/17 Javascript
html5+CSS 实现禁止IOS长按复制粘贴功能
2016/12/28 Javascript
JS轮播图实现简单代码
2021/02/19 Javascript
详解在WebStorm中添加Vue.js单文件组件的高亮及语法支持
2017/10/21 Javascript
浅谈mvvm-simple双向绑定简单实现
2018/04/18 Javascript
JS选取DOM元素常见操作方法实例分析
2018/12/10 Javascript
layui+jquery支持IE8的表格分页方法
2019/09/28 jQuery
js判断复选框是否选中的方法示例【基于jQuery】
2019/10/10 jQuery
解决vue-cli项目开发运行时内存暴涨卡死电脑问题
2019/10/29 Javascript
vue ssr+koa2构建服务端渲染的示例代码
2020/03/23 Javascript
原生js实现日期选择插件
2020/05/21 Javascript
js实现右键弹出自定义菜单
2020/09/08 Javascript
JavaScript WeakMap使用详解
2021/02/05 Javascript
[01:44]《为梦想出发》—联想杯DOTA2完美世界全国高校联赛
2015/09/30 DOTA
[44:43]完美世界DOTA2联赛决赛日 FTD vs GXR 第一场 11.08
2020/11/11 DOTA
使用Python的Flask框架构建大型Web应用程序的结构示例
2016/06/04 Python
Python中的迭代器与生成器高级用法解析
2016/06/28 Python
Python3.6正式版新特性预览
2016/12/15 Python
python3实现SMTP发送邮件详细教程
2018/06/19 Python
python实现基于信息增益的决策树归纳
2018/12/18 Python
python批量创建指定名称的文件夹
2019/03/21 Python
Python实现二叉树前序、中序、后序及层次遍历示例代码
2019/05/18 Python
python mysql自增字段AUTO_INCREMENT值的修改方式
2020/05/18 Python
HTML5 body设置全屏背景图片的示例代码
2020/12/08 HTML / CSS
数学国培研修感言
2014/02/13 职场文书
活动总结书
2014/05/08 职场文书
银行贷款收入证明
2014/10/17 职场文书
先进党支部申报材料
2014/12/24 职场文书
本科毕业论文指导教师评语
2014/12/30 职场文书