php并发加锁问题分析与设计代码实例讲解


Posted in PHP onFebruary 26, 2021

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

1 没有应用锁机制

1.1 财务支付简化版本代码

<!--?php 
/** 
 * pay.php 
 * 
 * 支付没有应用锁
 * 
 * Copy right (c) 2016 
 * 
 * modification history: 
 * -------------------- 
 * 2018/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操作人:

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

m操作人:

取出用户余额1000。
支付后剩余700 = 1000 - 300。
支付后账户余额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) 2018
 * 
 * modification history: 
 * -------------------- 
 * 2018/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) 2018 
 * 
 * modification history: 
 * -------------------- 
 * 2018/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操作人:

获取锁: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并发加锁问题分析与设计内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP高级OOP技术演示
Aug 27 PHP
MySQL连接数超过限制的解决方法
Jul 17 PHP
PHP中用hash实现的数组
Jul 17 PHP
php实现获取局域网所有用户的电脑IP和主机名、及mac地址完整实例
Jul 18 PHP
php相对当前文件include其它文件的方法
Mar 13 PHP
php ucwords() 函数将字符串中每个单词的首字符转换为大写(实现代码)
May 12 PHP
php中的登陆login实例代码
Jun 20 PHP
PHP获取用户客户端真实IP的解决方案
Oct 10 PHP
Laravel构建即时应用的一种实现方法详解
Aug 31 PHP
php curl简单采集图片生成base64编码(并附curl函数参数说明)
Feb 15 PHP
基于Laravel-admin 后台的自定义页面用法详解
Sep 30 PHP
关于PHP中interface的用处详解
Jul 26 PHP
PHP内存溢出优化代码详解
Feb 26 #PHP
php自动加载代码实例详解
Feb 26 #PHP
PHP的重载使用魔术方法代码实例详解
Feb 26 #PHP
PHP解密支付宝小程序的加密数据、手机号的示例代码
Feb 26 #PHP
php中get_object_vars()在数组的实例用法
Feb 22 #PHP
MacOS下PHP7.1升级到PHP7.4.15的方法
Feb 22 #PHP
关于PhpStorm设置点击编辑文件自动定位源文件的实现方式
Dec 30 #PHP
You might like
加强版phplib的DB类
2008/03/31 PHP
JS与PHP向函数传递可变参数的区别实例代码
2011/05/18 PHP
destoon后台网站设置变成空白的解决方法
2014/06/21 PHP
CI框架扩展系统核心类的方法分析
2016/05/23 PHP
Yii框架引入coreseek分页功能示例
2019/02/08 PHP
JavaScript的Cookies
2008/01/16 Javascript
JavaScript的eval JSON object问题
2009/11/15 Javascript
javascript学习笔记(十九) 节点的操作实现代码
2012/06/20 Javascript
JavaScript 学习笔记之数据类型
2015/01/14 Javascript
简介JavaScript中toTimeString()方法的使用
2015/06/12 Javascript
Bootstrap零基础学习第一课之模板
2016/07/18 Javascript
浅谈react前后端同构渲染
2017/09/20 Javascript
Node.js readline模块与util模块的使用
2018/03/01 Javascript
详解vue 数组和对象渲染问题
2018/09/21 Javascript
vue-cli 构建骨架屏的方法示例
2018/11/08 Javascript
解决echarts的多个折现数据出现坐标和值对不上的问题
2018/12/28 Javascript
JavaScript JMap类定义与使用方法示例
2019/01/22 Javascript
移动端手指操控左右滑动的菜单
2019/09/08 Javascript
JS实现商品橱窗特效
2020/01/09 Javascript
Ant Design moment对象和字符串之间的相互转化教程
2020/10/27 Javascript
详解Python中的条件判断语句
2015/05/14 Python
使用python3.5仿微软记事本notepad
2016/06/15 Python
Python 基础教程之闭包的使用方法
2017/09/29 Python
python3.6 实现AES加密的示例(pyCryptodome)
2018/01/10 Python
Django 限制用户访问频率的中间件的实现
2018/08/23 Python
查看Python依赖包及其版本号信息的方法
2019/08/13 Python
Python下利用BeautifulSoup解析HTML的实现
2020/01/17 Python
Python生成pdf目录书签的实例方法
2020/10/29 Python
举例讲解Python装饰器
2020/12/24 Python
Grid 宫格常用布局的实现
2020/01/10 HTML / CSS
创新比赛获奖感言
2014/02/13 职场文书
《莫泊桑拜师》教学反思
2014/04/23 职场文书
经典演讲稿汇总
2014/05/19 职场文书
学生退学证明
2015/06/23 职场文书
工商局调档介绍信
2015/10/22 职场文书
Java实现扫雷游戏详细代码讲解
2022/05/25 Java/Android