php多进程模拟并发事务产生的问题小结


Posted in PHP onDecember 07, 2018

前言

本文通过实例代码给大家介绍了关于php多进程模拟并发事务产生的一些问题,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧


drop table if exists `test`;
create table if not exists `test` (
 id int not null auto_increment , 
 count int default 0 , 
 primary key `id` (`id`)
) engine=innodb character set utf8mb4 collate = utf8mb4_bin comment '测试表';

insert into test (`count`) values (100);

php 代码

// 进程数量
$pro_count = 100;
$pids = [];
for ($i = 0; $i < $pro_count; ++$i)
{
 $pid = pcntl_fork();
 if ($pid < 0) {
  // 主进程
  throw new Exception('创建子进程失败: ' . $i);
 } else if ($pid > 0) {
  // 主进程
  $pids[] = $pid;
 } else {
  // 子进程
  try {
   $pdo = new PDO(...);
   $pdo->beginTransaction();
   $stmt = $pdo->query('select `count` from test');
   $count = $stmt->fetch(PDO::FETCH_ASSOC)['count'];
   $count = intval($count);
   if ($count > 0) {
    $count--;
    $pdo->query('update test set `count` = ' . $count . ' where id = 2');
   }
   $pdo->commit();
  } catch(Exception $e) {
   $pdo->rollBack(); 
   throw $e;
  }
  // 退出子进程
  exit;
 }
}

期望的结果

期望 count 字段减少的量超过 100,变成负数!也就是多减!

实际结果

并发 200 的情况下,运行多次后的结果分别如下:

1. count = 65
2. count = 75
3. count = 55
4. count = 84
...

与期望结果相差甚远!为什么会出现这样的现象呢?

解释

首先清楚下目前的程序运行环境,并发场景。何为并发,几乎同时执行,称之为并发。具体解释如下:

进程 过程 获取 更新
1-40 同时创建并运行 100 99
41-80 同时创建并运行 99 98
81 - 100 同时创建并运行 98 97

对上述第一行做解释,第 1-40 个子进程的创建几乎同时,运行也几乎同时:

进程 1 获取 count = 100,更新 99
进程 2 获取 count = 100,更新 99
...
进程 40 获取 count = 100,更新 99

所以,实际上这些进程都做了一致的操作,并没有按照预期的那样:进程1 获取 count=100,更新 99;进程 2 获取进程1更新后的结果 count=99,更新98;...;进程 99 获取进程 98更新后的结果count=1,更新0
,产生的现象就是少减了!!

结论

采用上述做法实现的程序,库存总是 >= 0。

疑问

那要模拟超库存的场景该如何设计程序呢?

仍然采用上述代码,将以下代码:

if ($count > 0) {
 $count--;
 $pdo->query('update test set `count` = ' . $count . ' where id = 2');
}

修改成下面这样:

if ($count > 0) {
 $pdo->query('update test set `count` = `count` - 1 where id = 2');
}

结果就会出现超库存!!

库存 100,并发 200,最终库存减少为 -63。为什么会出现这样的情况呢?以下描述了程序运行的具体过程

进程 1 获取库存 100,更新 99
进程 2 获取库存 100,更新 98(99 - 1)
进程 3 获取库存 100,更新 97(98 - 1)
....
进程 168 获取库存 1 ,更新 0(1-1)
进程 169 获取库存 1 ,更新 -1(0 - 1)
进程 170 获取库存 1 ,更新 -2(-1 - 1)
....
进程 200 获取库存 1,更新 -63(-62 - 1)

现在看来很懵逼,实际就是下面这条语句导致的:

$pdo->query('update test set `count` = `count` - 1 where id = 2');

这边详细阐述 进程 1,简称 a;进程 2,简称 b 他们具体的执行顺序:

1. a 查询到库存 100
2. b 查询到库存 100
3. a 更新库存为 99(100 - 1),这个应该秒懂
4. b 更新库存为 98(99 - 1)
- b 在执行更新操作的时候拿到的是 a 更新后的库存!
- 为什么会这样?因为更新语句是 `update test set count = count - 1 where id = 2`

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
PHP基础学习之流程控制的实现分析
Apr 28 PHP
Linux编译升级php的详细方法
Nov 04 PHP
PHP中可以自动分割查询字符的Parse_str函数使用示例
Jul 25 PHP
PHP中round()函数对浮点数进行四舍五入的方法
Nov 19 PHP
Windows下PHP开发环境搭建教程(Apache+PHP+MySQL)
Jun 13 PHP
PHP微信刮刮卡 附微信接口
Jul 22 PHP
Yii2中简单的场景使用介绍
Jun 02 PHP
Windows 下安装 swoole 图文教程(php)
Jun 05 PHP
通过源码解析Laravel的依赖注入
Jan 22 PHP
php从数据库读取数据,并以json格式返回数据的方法
Aug 21 PHP
PHP文件后缀不强制为.php方法
Mar 31 PHP
laravel返回统一格式错误码问题
Nov 04 PHP
Ubuntu 16.04中Laravel5.4升级到5.6的步骤
Dec 07 #PHP
PHP ajax+jQuery 实现批量删除功能实例代码小结
Dec 06 #PHP
PHP实现简易计算器功能
Aug 28 #PHP
laravel5实现微信第三方登录功能
Dec 06 #PHP
PHP实现简单计算器小程序
Aug 28 #PHP
ThinkPHP 3.2.3实现加减乘除图片验证码
Dec 05 #PHP
php实现算术验证码功能
Dec 05 #PHP
You might like
mysql,mysqli,PDO的各自不同介绍
2012/09/19 PHP
php smarty truncate UTF8乱码问题解决办法
2014/06/13 PHP
PHP中STDCLASS用法实例分析
2016/11/11 PHP
PHP调用接口API封装的例子
2019/10/11 PHP
jQuery 标题的自动翻转实现代码
2009/10/14 Javascript
jquery ajax跨域解决方法(json方式)
2014/02/04 Javascript
JS+CSS实现带关闭按钮DIV弹出窗口的方法
2015/02/27 Javascript
详解JavaScript对Date对象的操作问题(生成一个倒数7天的数组)
2015/10/01 Javascript
js+ajax实现获取文件大小的方法
2015/12/08 Javascript
Node.js的Mongodb使用实例
2016/12/30 Javascript
bootstrap table动态加载数据示例代码
2017/03/25 Javascript
js 索引下标之li集合绑定点击事件
2018/01/12 Javascript
JS弹窗 JS弹出DIV并使整个页面背景变暗功能的实现代码
2018/04/21 Javascript
element el-input directive数字进行控制
2018/10/11 Javascript
VUE路由动态加载实例代码讲解
2019/08/26 Javascript
完美解决vue 中多个echarts图表自适应的问题
2020/07/19 Javascript
js实现幻灯片轮播图
2020/08/14 Javascript
JS实现简易贪吃蛇游戏
2020/08/24 Javascript
element中Steps步骤条和Tabs标签页关联的解决
2020/12/08 Javascript
8个非常实用的Vue自定义指令
2020/12/15 Vue.js
[27:08]完美世界DOTA2联赛PWL S2 SZ vs Rebirth 第二场 11.21
2020/11/23 DOTA
Python中的异常处理学习笔记
2015/01/28 Python
Flask框架实现给视图函数增加装饰器操作示例
2018/07/16 Python
Python中psutil的介绍与用法
2019/05/02 Python
PyQt5实现简易电子词典
2019/06/25 Python
Django实现列表页商品数据返回教程
2020/04/03 Python
关于Python解包知识点总结
2020/05/05 Python
python中remove函数的踩坑记录
2021/01/04 Python
python3.9和pycharm的安装教程并创建简单项目的步骤
2021/02/03 Python
html5 touch事件实现触屏页面上下滑动(一)
2016/03/10 HTML / CSS
世界上最大的糖果店:Dylan’s Candy Bar
2017/11/07 全球购物
美国玩具公司:U.S.Toy
2018/05/19 全球购物
中学优秀班主任事迹材料
2014/05/01 职场文书
单位单身证明样本
2014/10/11 职场文书
优秀教师工作总结2015
2015/07/22 职场文书
jquery插件实现搜索历史
2021/04/24 jQuery