php并发加锁
CleverCode在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误。下面CleverCode将分析一个财务支付锁的问题。
1 没有应用锁机制
1.1 财务支付简化版本代码
- <?php
- /**
- * pay.php
- *
- * 支付没有应用锁
- *
- * Copy right (c) 2016 http://blog.csdn.net/CleverCode
- *
- * 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 类图设计如下
2.2 php源码设计如下
LockSystem.php
- <?php
- /**
- * LockSystem.php
- *
- * php锁机制
- *
- * Copy right (c) 2016 http://blog.csdn.net/CleverCode
- *
- * 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 http://blog.csdn.net/CleverCode
- *
- * 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并发加锁的更多相关文章
- ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁
前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...
- php并发加锁示例
在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误.下面我将分析一个财务支付锁的问题.希望对大家有所帮助. 1 没有应用锁机制 1.1 财务支付简化版本代 ...
- PHP_MySQL高并发加锁事务处理
1.背景: 现在有这样的需求,插入数据时,判断test表有无username为‘mraz’的数据,无则插入,有则提示“已插入”,目的就是想只插入一条username为‘mraz’的记录. 2.一般程序 ...
- Java并发(9)- 从同步容器到并发容器
引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线程安全的, ...
- 【整理】互联网服务端技术体系:高性能之并发(Java)
分而合之,并行不悖. 综述入口见:"互联网应用服务端的常用技术思想与机制纲要" 引子 并发,就是在同一时间段内有多个任务同时进行着.这些任务或者互不影响互不干扰,或者共同协作来完成 ...
- C# 设计模式巩固 - 单例模式
前言 设计模式的文章很多,所以此文章只是为了巩固一下自己的基础,说的不详细请见谅. 介绍 - 单例模式 官方定义:确保一个类只有一个实例,并提供一个全局访问点. 通俗定义:就是一个类只有一个单个实例. ...
- Java之架构(0) - 架构之路
软件架构作为一个概念,体现在技术和业务两个方面. 从技术角度来说:软件架构随着技术的革新不断地更新其内容,软件架构建立于当前技术和一些基本原则的基础之上. 先说一些基本原则: 分层原则:分层是为了降低 ...
- 为什么我要选择erlang+go进行server架构(2)
原创文章,转载请注明出处:server非业余研究http://blog.csdn.net/erlib 作者Sunface 为什么我要选择Erlang呢? 一.erlang特别适合中小团队创业: erl ...
- AQS系列(六)- Semaphore的使用及原理
前言 Semaphore也是JUC包中一个用于并发控制的工具类,举个常用场景的例子:有三台电脑五个人,每个人都要用电脑注册一个自己的账户,这时最开始只能同时有三个人操作电脑注册账户,这三个人中有人操作 ...
随机推荐
- Scratch2的鸡兔同笼
解题思路鸡兔同笼新算法:已知共有鸡和兔15只,共有40只脚,问鸡和兔各有几只.算法:假设鸡和兔训练有素,吹一声哨,它们抬起一只脚,(40-15=25) .再吹一声哨,它们又抬起一只脚,(25-15=1 ...
- Hadoop(四):HDFS读数据的基本流程
HDFS读数据的流程 shell发送下载请求 NameNode检测文件系统,查找a的元数据(block和block所在的位置信息) 返回元数据给shell,返回的元数据会排序,排序规则: 拓扑距离近排 ...
- C/C++内存详解
众所周知,堆和栈是数据结构中的两种数据结构类型,堆是一种具有优先顺序的完全二叉树(或者说是一种优先队列,因为它在一定的优先顺序下满足队列先进先出的特点),排队打饭就是它的典型实例,栈是一种后进先出的数 ...
- Array(数组)对象-->reverse() 方法
1.定义和用法 reverse() 方法用于颠倒数组中元素的顺序:倒序. 语法: array.reverse() 举例: var arr = [1,2,3,4,5]; console.log(arr. ...
- 条件变量 condition_variable wait_for
wait_for(阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后) #include <iostream> #include <atomic> #include < ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(六)之Initialization & Cleanup
Two of these safety issues are initialization and cleanup. initialization -> bug cleanup -> ru ...
- python这门语言为什么要起这个名字
我只是一只可爱的小虫 前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:Liz喵 PS:如有需要Python学习资料的小 ...
- 【Jenkins】插件更改国内源
最近调试脚本,本机安装了Jenkins,但是安装插件时一直失败.更改升级站点也不生效,究其原因是因为default.json中插件下载地址还https://updates.jenkins.io,升级站 ...
- windows UAC 提权实验(CVE-2019-1388)
--------------------------------------------------------------------------------- 声明:本文仅做学习,实验主机为虚拟机 ...
- Java环境下 selenium webDriver + chrome浏览器搭建与调试
一.首先下载selenium webDriver jar包,下载地址如下: http://selenium-release.storage.googleapis.com/index.html 二.下载 ...