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 相关文章推荐
图片存储与浏览一例(Linux+Apache+PHP+MySQL)
Oct 09 PHP
第五节--克隆
Nov 16 PHP
php面向对象的方法重载两种版本比较
Sep 08 PHP
php实现首页链接查询 友情链接检查的代码
Jan 05 PHP
PHP高手需要要掌握的知识点
Aug 21 PHP
PHP实现UTF-8文件BOM自动检测与移除实例
Nov 05 PHP
thinkphp区间查询、统计查询与SQL直接查询实例分析
Nov 24 PHP
使用PHPCMS搭建wap手机网站
Sep 20 PHP
ThinkPHP和UCenter接口冲突的解决方法
Jul 25 PHP
PHP通过调用新浪API生成t.cn格式短网址链接的方法详解
Feb 20 PHP
laravel实现查询最后执行的一条sql语句的方法
Oct 09 PHP
PHP dirname(__FILE__)原理及用法解析
Oct 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版淘宝网查询商品接口代码示例
2014/06/17 PHP
php检查字符串中是否包含7位GSM字符的方法
2015/03/17 PHP
PHP中的事务使用实例
2015/05/26 PHP
ext 列表页面关于多行查询的办法
2010/03/25 Javascript
IE6背景图片不缓存问题解决方案及图片使用策略多个方法小结
2012/05/14 Javascript
引用外部js乱码问题分析及解决方案
2013/04/12 Javascript
js自动生成的元素与页面原有元素发生堆叠的解决方法
2013/10/24 Javascript
jquery阻止后续事件只执行第一个事件
2014/07/24 Javascript
jQuery实现按键盘方向键翻页特效
2015/03/18 Javascript
JQuery中属性过滤选择器用法实例分析
2015/05/18 Javascript
Javascript编写俄罗斯方块思路及实例
2015/07/07 Javascript
基于javascript实现漂亮的页面过渡动画效果附源码下载
2015/10/26 Javascript
BootStrap智能表单实战系列(八)表单配置json详解
2016/06/13 Javascript
canvas实现绘制吃豆鱼效果
2017/01/12 Javascript
vue如何引用其他组件(css和js)
2017/04/13 Javascript
JS中mouseup事件丢失的原因与解决办法
2017/06/14 Javascript
Angular模板表单校验方法详解
2017/08/11 Javascript
微信小程序项目实践之验证码倒计时功能
2018/07/18 Javascript
vue实现移动端图片上传功能
2019/12/23 Javascript
Vue 按照创建时间和当前时间显示操作(刚刚,几小时前,几天前)
2020/09/10 Javascript
Python读取环境变量的方法和自定义类分享
2014/11/22 Python
详解Python中列表和元祖的使用方法
2015/04/25 Python
Python实现堆排序的方法详解
2016/05/03 Python
Python标准库06之子进程 (subprocess包) 详解
2016/12/07 Python
Python编程之gui程序实现简单文件浏览器代码
2017/12/08 Python
Python paramiko模块使用解析(实现ssh)
2019/08/30 Python
python如何进行矩阵运算
2020/06/05 Python
Python单元测试及unittest框架用法实例解析
2020/07/09 Python
Python pathlib模块使用方法及实例解析
2020/10/05 Python
HTML5 中新的全局属性(整理)
2013/07/31 HTML / CSS
详解如何使用rem或viewport进行移动端适配
2020/08/14 HTML / CSS
ProBikeKit新西兰:自行车套件,跑步和铁人三项装备
2017/04/05 全球购物
经贸专业毕业生求职信
2014/03/23 职场文书
党员承诺书怎么写
2014/05/20 职场文书
ThinkPHP5和ThinkPHP6的区别
2021/03/31 PHP
Python OpenCV超详细讲解调整大小与图像操作的实现
2022/04/02 Python