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 相关文章推荐
优化使用mysql存储session的php代码
Jan 10 PHP
Laravel 4 初级教程之Pages、表单验证
Oct 30 PHP
php中time()与$_SERVER[REQUEST_TIME]用法区别
Nov 19 PHP
[原创]php获取数组中键值最大数组项的索引值
Mar 17 PHP
php抽象方法和抽象类实例分析
Dec 07 PHP
PHP二维数组去重算法
Dec 17 PHP
PHP中使用OpenSSL生成证书及加密解密
Feb 05 PHP
在IIS下安装PHP扩展的方法(超简单)
Apr 10 PHP
Windows平台实现PHP连接SQL Server2008的方法
Jul 26 PHP
PDO操作MySQL的基础教程(推荐)
Aug 18 PHP
PHP实现基本留言板功能原理与步骤详解
Mar 26 PHP
PHP实现简易用户登录系统
Jul 10 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+FLASH实现上传文件进度条相关文件 下载
2007/07/21 PHP
在PHP中操作Excel实例代码
2010/04/29 PHP
php中opendir函数用法实例
2014/11/15 PHP
深入理解PHP变量的值类型和引用类型
2015/10/21 PHP
PHP开发中常用的十个代码样例
2016/02/02 PHP
Laravel 创建可以传递参数 Console服务的例子
2019/10/14 PHP
键盘控制事件应用教程大全
2006/11/24 Javascript
JavaScript 匿名函数(anonymous function)与闭包(closure)
2011/10/04 Javascript
JavaScript打印网页指定区域的例子
2014/05/03 Javascript
详解JavaScript基于面向对象之创建对象(1)
2015/12/10 Javascript
莱鸟介绍javascript onclick事件
2016/01/06 Javascript
JavaScript判断数组是否存在key的简单实例
2016/08/03 Javascript
jquery 实现复选框的全选操作实例代码
2017/01/24 Javascript
JS中使用 after 伪类清除浮动实例
2017/03/01 Javascript
WebSocket的简单介绍及应用
2019/05/23 Javascript
[02:25]DOTA2英雄基础教程 虚空假面
2014/01/02 DOTA
[05:53]敌法师的金色冠名ID"BurNIng",是传说,是荣耀
2020/07/11 DOTA
详解Python中的序列化与反序列化的使用
2015/06/30 Python
浅析Python3爬虫登录模拟
2018/02/07 Python
python基础教程项目四之新闻聚合
2018/04/02 Python
python验证码识别教程之利用滴水算法分割图片
2018/06/05 Python
python中adb有什么功能
2020/06/07 Python
基于python实现坦克大战游戏
2020/10/27 Python
使用Python制作一个数据预处理小工具(多种操作一键完成)
2021/02/07 Python
Python命令行参数argv和argparse该如何使用
2021/02/08 Python
一款CSS3实现多功能下拉菜单(带分享按)的教程
2014/11/05 HTML / CSS
美国休闲服装品牌:Express
2016/09/24 全球购物
大学生职业生涯规划范文
2013/12/31 职场文书
办公室文员自荐书
2014/02/03 职场文书
《画风》教学反思
2014/04/16 职场文书
农业开发项目建议书
2014/05/16 职场文书
股票投资建议书
2014/05/19 职场文书
实验心得体会
2014/09/05 职场文书
教师批评与自我批评材料
2014/10/16 职场文书
优秀员工事迹材料
2014/12/20 职场文书
如何避免mysql启动时错误及sock文件作用分析
2022/01/22 MySQL