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中使用Oracle数据库(4)
Oct 09 PHP
php安全配置 如何配置使其更安全
Dec 16 PHP
PHP命名空间(Namespace)的使用详解
May 04 PHP
php实现微信公众平台账号自定义菜单类
Dec 02 PHP
php获取json数据所有的节点路径
May 17 PHP
php实现模拟post请求用法实例
Jul 11 PHP
PHP的命令行命令使用指南
Aug 18 PHP
使用PHP uniqid函数生成唯一ID
Nov 18 PHP
PHP生成唯一ID之SnowFlake算法
Dec 17 PHP
PHP实现根据数组的值进行分组的方法
Apr 20 PHP
php面向对象的用户登录身份验证
Jun 08 PHP
PHP实现的登录页面信息提示功能示例
Jul 24 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
解析PHP工厂模式的好处
2013/06/18 PHP
PHP Session 变量的使用方法详解与实例代码
2013/09/11 PHP
ThinkPHP模板Volist标签嵌套循环输出多维数组的方法
2016/03/23 PHP
Javascript实例教程(19) 使用HoTMetal(5)
2006/12/23 Javascript
js下拉框二级关联菜单效果代码具体实现
2013/08/03 Javascript
关于jquery的多个选择器的使用示例
2013/10/18 Javascript
javascript实现复选框选中属性
2015/03/25 Javascript
javascript判断数组内是否重复的方法
2015/04/21 Javascript
jQuery实现鼠标点击弹出渐变层的方法
2015/07/09 Javascript
JQUERY实现网页右下角固定位置展开关闭特效的方法
2015/07/27 Javascript
JavaScript获取键盘按键的键码(参照表)
2017/01/10 Javascript
使用bootstrap实现下拉框搜索功能的实例讲解
2018/08/10 Javascript
基于mpvue搭建微信小程序项目框架的教程详解
2019/04/10 Javascript
小程序实现搜索框
2020/06/19 Javascript
vuex入门最详细整理
2020/03/04 Javascript
vue单文件组件无法获取$refs的问题
2020/06/24 Javascript
使用grappelli为django admin后台添加模板
2014/11/18 Python
Django小白教程之Django用户注册与登录
2016/04/22 Python
python爬虫_自动获取seebug的poc实例
2017/08/05 Python
用Python实现随机森林算法的示例
2017/08/24 Python
selenium+python实现自动化登录的方法
2018/09/04 Python
python得到电脑的开机时间方法
2018/10/15 Python
python调用虹软2.0第三版的具体使用
2019/02/22 Python
Python按钮的响应事件详解
2019/03/04 Python
Python环境Pillow( PIL )图像处理工具使用解析
2019/09/12 Python
Django values()和value_list()的使用
2020/03/31 Python
Django表单提交后实现获取相同name的不同value值
2020/05/14 Python
ASP.NET Core中的配置详解
2021/02/05 Python
Marriott国际:万豪国际酒店查询预订
2017/09/25 全球购物
YII2 全局异常处理深入讲解
2021/03/24 PHP
2015学生会文艺部工作总结
2015/04/03 职场文书
人事任命书范本
2015/09/21 职场文书
严以律己专题学习研讨会发言材料
2015/11/09 职场文书
Nginx工作原理和优化总结。
2021/04/02 Servers
pytorch 实现多个Dataloader同时训练
2021/05/29 Python
利用 JavaScript 构建命令行应用
2021/11/17 Javascript