php并发加锁示例
在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误。下面我将分析一个财务支付锁的问题。希望对大家有所帮助。
1 没有应用锁机制
1.1 财务支付简化版本代码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
<!--?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操作人:
- 取出用户的余额1000。
- 支付后剩余 800 = 1000 - 200。
- 更新后账户余额800。
m操作人:
- 取出用户余额1000。
- 支付后剩余700 = 1000 - 300。
- 支付后账户余额700。
两次支付后,账户的余额居然还有700,应该的情况是花费了500,账户余额500才对。造成这个现象的根本原因,是并发的时候,p和m同时操作取到的余额数据都是1000。
2 加锁设计
锁的操作一般只有两步,一 获取锁(getLock);二是释放锁(releaseLock)。但现实锁的方式有很多种,可以是文件方式实现;sql实现;Memcache实现;根据这种场景我们考虑使用策略模式。
2.1 类图设计如下

2.2 php源码设计如下
LockSystem.php
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
<!--?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 支付系统应用锁
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
<!--?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操作人:
- 获取锁:pay100
- 取出用户的余额1000。
- 支付后剩余 800 = 1000 - 200。
- 更新后账户余额800。
- 释放锁:pay100
m操作人:
- 等待锁:pay100
- 获取锁:pay100
- 获取余额:800
- 支付后剩余500 = 800 - 300。
- 支付后账户余额500。
- 释放锁:pay100
两次支付后,余额500。非常完美了解决了并发造成的临界区资源的访问问题。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
php并发加锁示例的更多相关文章
- 分析一个MySQL并发事务示例
小结: 1. https://mp.weixin.qq.com/s/hdDl95a6ayVtCoEc3RiLwQ 分析一个MySQL并发事务示例 性能与架构 1月12日 MySQL实战45讲 从原 ...
- ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁
前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...
- Go语言并发编程示例 分享(含有源代码)
GO语言并发示例分享: ppt http://files.cnblogs.com/files/yuhan-TB/GO%E8%AF%AD%E8%A8%80.pptx 代码, 实际就是<<Go ...
- java一些常用并发工具示例
最近把<java并发编程实战>-Java Consurrency in Practice 重温了一遍,把书中提到的一些常用工具记录于此: 一.闭锁(门栓)- CountDownLatch ...
- php并发加锁
CleverCode在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误.下面CleverCode将分析一个财务支付锁的问题. 1 没有应用锁机制 1.1 ...
- python多线程限制并发数示例
#coding: utf-8 #!/usr/bin/env python import Queue import threading import time prolock = threading.L ...
- PHP使用文件锁解决高并发问题示例
新建一个.txt文件,文件中什么都不用写. [一].阻塞(等待)模式:(只要有其他进程已经加锁文件,当前进程会一直等其他进程解锁文件) <?php //连接数据库 $con=mysqli_con ...
- 【Todo】Java并发学习 & 示例练习及代码
接上一篇:http://www.cnblogs.com/charlesblc/p/6097111.html <Java并发学习 & Executor学习 & 异常逃逸 & ...
- PHP_MySQL高并发加锁事务处理
1.背景: 现在有这样的需求,插入数据时,判断test表有无username为‘mraz’的数据,无则插入,有则提示“已插入”,目的就是想只插入一条username为‘mraz’的记录. 2.一般程序 ...
随机推荐
- Example006为弹出窗口加入关闭按钮
<!-- 实例006为弹出的窗口加入关闭按钮 --> <head> <meta charset="UTF-8"> </head> & ...
- 2.如何实现使用VBS脚本程序对直播间自动评论
前言:本文使用的是VBS脚本,实现了对繁星直播自动登录,自动进入房间并且自动评论. 前提准备:把需要刷的评论放到mysql中,再使用vbs读出评论 -------------------------- ...
- html5小游戏基础知识
显示一个DIV和隐藏一个DIV 首先,我们要显示一个DIV和隐藏一个DIV需要使用css里面使用: .hide{ display:none;} .show{display:block;} 在需要显示或 ...
- nodejs模块学习: connect解析
nodejs模块学习: connect解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需要开发者创造大量的轮子来解决 ...
- Loadrunner--自动关联和手动关联
2017-06-09 15:32:45个人也属于刚刚开始学习,有什么不对的地方敬请指导:qq:389791447 一开始的时候,准备去学习怎么去关联.一时也毛不着头脑,就在网上找了一些视频看,有的人说 ...
- npm 的用法
当用npm 安装依赖时如果加上 --save 就会自动把依赖模块添加到package.json中 别人下载时直接npm install 加载后就可以了
- PAT-1099(Build A Binary Search Tree)
题目见这里 分析:分四步进行 1)根据给定的结点情况建二叉树 2)对输入的键值排序(asending) 3)对二叉树中序遍历,同时对应赋key值 4)层次遍历(队列应用) 题目并不困难,但是我误入了 ...
- 在线制作css动画——cssanimate
熟悉CSS的人都知道,CSS可以实现很多漂亮的动画,特别是它的在线功能,能够帮助人们解决很多制作动画的效果.今天特别推荐一个在线CSS插件功能--cssanimate,这个最大的特色就是以图形界面方式 ...
- Eclipse工程有乱码
处理:把整个工程的“Text file encoding”属性设为GBK,就不会有乱码了.设置方法:在eclipse中右击工程,点击弹出框最下面的“Properties”,然后在弹出的窗口左侧点击“R ...
- 用java调用oracle存储过程总结
以前一直没有动存储过程是用来干嘛的,后来请教朋友才换为自己的理解方式,用自己通俗的语言来说,就是把sql语句换为一个过程,也可以说是一个方法,每次直接给参数调用就好,使用存储过程查询速度快,系统只编译 ...