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 相关文章推荐
多文件上载系统完整版
Oct 09 PHP
dedecms 制作模板中使用的全局标记图文教程
Mar 11 PHP
Windows IIS PHP 5.2 安装与配置方法
Jun 08 PHP
php microtime获取浮点的时间戳
Feb 21 PHP
浅谈php serialize()与unserialize()的用法
Jun 05 PHP
PHP_Cooikes不同页面无法传递的解决方法
Mar 07 PHP
教你如何在CI框架中使用 .htaccess 隐藏url中index.php
Jun 09 PHP
php计算两个文件相对路径的方法
Mar 14 PHP
在WordPress的后台中添加顶级菜单和子菜单的函数详解
Jan 11 PHP
详解php几行代码实现CSV格式文件输出
Jul 01 PHP
实例讲解PHP验证邮箱是否合格
Jan 28 PHP
php 原生分页
Apr 01 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
Protoss建筑一览
2020/03/14 星际争霸
PHP+DBM的同学录程序(1)
2006/10/09 PHP
在PHP里得到前天和昨天的日期的代码
2007/08/16 PHP
火车采集器 免费版使出收费版本功能实现原理
2009/09/17 PHP
一个PHP实现的轻量级简单爬虫
2015/07/08 PHP
PHP isset()与empty()的使用区别详解
2017/02/10 PHP
PHP使用标准库spl实现的观察者模式示例
2018/08/04 PHP
Laravel源码解析之路由的使用和示例详解
2018/09/27 PHP
swoole_process实现进程池的方法示例
2018/10/29 PHP
一个js实现的所谓的滑动门
2007/05/23 Javascript
Jquery 获取对象的几种方式介绍
2014/01/17 Javascript
javascript中的五种基本数据类型
2015/08/26 Javascript
JS实现兼容性较好的随屏滚动效果
2015/11/09 Javascript
微信小程序 封装http请求实例详解
2017/01/16 Javascript
二维码图片生成器QRCode.js简单介绍
2017/08/18 Javascript
解决layui table表单提示数据接口请求异常的问题
2019/09/24 Javascript
vue-router 控制路由权限的实现
2020/09/24 Javascript
Vue中inheritAttrs的使用实例详解
2020/12/31 Vue.js
浅谈Vue开发人员的7个最好的VSCode扩展
2021/01/20 Vue.js
Python中的复制操作及copy模块中的浅拷贝与深拷贝方法
2016/07/02 Python
Python 查看文件的编码格式方法
2017/12/21 Python
tornado 多进程模式解析
2018/01/15 Python
Python pandas.DataFrame 找出有空值的行
2019/09/09 Python
python计算波峰波谷值的方法(极值点)
2020/02/18 Python
keras绘制acc和loss曲线图实例
2020/06/15 Python
python连接mysql有哪些方法
2020/06/24 Python
基于Python制作一副扑克牌过程详解
2020/10/19 Python
HTMl5的存储方式sessionStorage和localStorage详解
2014/03/18 HTML / CSS
英国知名的皮手套品牌:Dents
2016/11/13 全球购物
学生党支部先进事迹
2014/02/04 职场文书
小学课外阅读总结
2014/07/09 职场文书
2015新年联欢晚会开场白
2014/12/14 职场文书
导游词之南昌滕王阁
2019/11/29 职场文书
七年级作文之下雨天
2019/12/23 职场文书
idea 在springboot中使用lombok插件的方法
2021/08/02 Java/Android
口袋妖怪冰系十大最强精灵,几何雪花排第七,第六类似北极熊
2022/03/18 日漫