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 相关文章推荐
ip签名探针
Oct 09 PHP
用PHP程序实现支持页面后退的两种方法
Jun 30 PHP
PHP mb_convert_encoding 获取字符串编码类型实现代码
Apr 26 PHP
PHP正则的Unknown Modifier错误解决方法
Mar 02 PHP
在IIS7.0下面配置PHP 5.3.2运行环境的方法
Apr 13 PHP
PHP 事件机制(2)
Mar 23 PHP
php模板原理讲解
Nov 13 PHP
Smarty局部缓存的几种方法简介
Jun 17 PHP
php数组分页实现方法
Apr 30 PHP
php好代码风格的阶段性总结
Jun 25 PHP
解决form中action属性后面?传递参数 获取不到的问题
Jul 21 PHP
ThinkPHP5.1框架页面跳转及修改跳转页面模版示例
May 06 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中cookie和session的区别及cookie和session用法小结
2016/06/12 PHP
javascript Zifa FormValid 0.1表单验证 代码打包下载
2007/06/08 Javascript
js使用正则实现ReplaceAll全部替换的方法
2014/08/22 Javascript
JS模拟酷狗音乐播放器收缩折叠关闭效果代码
2015/10/29 Javascript
基于javascript实现动态显示当前系统时间
2016/01/28 Javascript
JS中call/apply、arguments、undefined/null方法详解
2016/02/15 Javascript
BootStrap glyphicons 字体图标实现方法
2016/05/01 Javascript
jQuery 获取屏幕高度、宽度的简单实现案例
2016/05/17 Javascript
用js屏蔽被http劫持的浮动广告实现方法
2017/08/10 Javascript
详解react阻止无效重渲染的多种方式
2018/12/11 Javascript
vue2.0+vue-router构建一个简单的列表页的示例代码
2019/02/13 Javascript
JavaScript 继承 封装 多态实现及原理详解
2019/07/29 Javascript
Vuex,iView UI面包屑导航使用扩展详解
2019/11/04 Javascript
JS实现压缩上传图片base64长度功能
2019/12/03 Javascript
浅谈JavaScript窗体Window.ShowModalDialog使用
2020/07/22 Javascript
vue实现移动端H5数字键盘组件使用详解
2020/08/25 Javascript
详解javascript脚本何时会被执行
2021/02/05 Javascript
[00:17]DOTA2荣耀之路5:It’s a disastah!
2018/05/28 DOTA
Python 执行字符串表达式函数(eval exec execfile)
2014/08/11 Python
Python读写配置文件的方法
2015/06/03 Python
Windows下为Python安装Matplotlib模块
2015/11/06 Python
Python序列操作之进阶篇
2016/12/08 Python
使用python获取电脑的磁盘信息方法
2018/11/01 Python
python 随机打乱 图片和对应的标签方法
2018/12/14 Python
python读写csv文件并增加行列的实例代码
2019/08/01 Python
解决django FileFIELD的编码问题
2020/03/30 Python
html5声频audio和视频video等新特性详细说明
2012/12/26 HTML / CSS
复古斯堪的纳维亚儿童服装:Baby go Retro
2017/09/09 全球购物
皇家道尔顿官网:Royal Doulton
2017/12/06 全球购物
美国宠物用品网站:Value Pet Supplies
2018/03/17 全球购物
JBL澳大利亚官方商店:扬声器、耳机和音响系统
2018/05/24 全球购物
平面设计师的工作职责
2013/11/21 职场文书
社区学雷锋活动策划方案
2014/01/30 职场文书
2015年教师节贺卡寄语
2015/03/24 职场文书
初中语文教学研修日志
2015/11/13 职场文书
spring cloud 配置中心native配置方式
2021/09/25 Java/Android