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.一般程序 ...
随机推荐
- phpcmsV9常用标签
头部: <title>{if isset($SEO['title']) && !empty($SEO['title'])}{$SEO['title']}{/if}{$SEO ...
- Handlebars.js 模板引擎
介绍 Handlebars 是 JavaScript 一个语义模板库,通过对view和data的分离来快速构建Web模板.它采用"Logic-less template"(无逻辑模 ...
- python学习笔记之运算符
目录 前言 软件环境 身份运算符 算术运算符 比较运算符 位移运算符 自变运算符 位运算符 逻辑运算符 成员关系运算符 Python真值表 最后 前言 在前面的博文介绍了Python的数据结构之后,接 ...
- TCP错误恢复特性之一TCP重传
TCP的错误恢复特性是我们用来定位.诊断并最终修复网络高延迟的最好工具. 常见的TCP错误恢复特性有:TCP重传.TCP重复确认和快速重传 1. TCP重传: 重传数据包是TCP最基本的错误恢复特性之 ...
- 用Left join代替not in
很多人都知道 在各种数据库里面 not in 的效率极其低下.例如 select * from a where a.id not in ( select id from b ) 我们假如a表有 10万 ...
- CSS 回流(reflow)
摘录: 1. 回流 回流是指浏览器为了重新渲染部分或者全部的文档而重新计算文档中元素的位置和几何构造的过程. 因为回流可能导致整个dom树的重新构造,所以会影响性能. 2. display:no ...
- Luogu 1559 运动员最佳匹配问题(带权二分图最大匹配)
Luogu 1559 运动员最佳匹配问题(带权二分图最大匹配) Description 羽毛球队有男女运动员各n人.给定2 个n×n矩阵P和Q.P[i][j]是男运动员i和女运动员j配对组成混合双打的 ...
- CJOJ 1010【NOIP2003】加分二叉树 / Luogu 1040 加分二叉树(树型动态规划)
CJOJ 1010[NOIP2003]加分二叉树 / Luogu 1040 加分二叉树(树型动态规划) Description 设 一个 n 个节点的二叉树 tree 的中序遍历为( 1,2,3,-, ...
- 跨域访问http接口的使用
最近在弄一个sip网页集成版软电话,为了功能的完善,呼叫中心的工作人员为我们提供了一个http接口,我先在网页中直接打开分析了一下他的返回值,然后又放到js中去访问,结果一放到js中一访问就发现浏览器 ...
- Oracle PIVOT 行转列方法
数据库中業種的存储如下图: SELECT * FROM M_TORIHIKISAKI_GYOSYU 其中GYIUSYU_CD字段代表不同的業種 而画面需要实现下图所示样式:(将每条数据的業種横向展开显 ...