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网页后退不再出现过期
Mar 08 PHP
删除数组元素实用的PHP数组函数
Aug 18 PHP
解决PHP在DOS命令行下却无法链接MySQL的技术笔记
Dec 29 PHP
header跳转和include包含问题详解
Sep 08 PHP
浅析php header 跳转
Jun 17 PHP
php使用mkdir创建多级目录入门例子
May 10 PHP
phpmailer在服务器上不能正常发送邮件的解决办法
Jul 08 PHP
PHP编译安装中遇到的两个错误和解决方法
Aug 20 PHP
PHP中使用php5-ffmpeg撷取视频图片实例
Jan 07 PHP
PHP hex2bin()函数用法讲解
Feb 25 PHP
TP5(thinkPHP5框架)基于bootstrap实现的单图上传插件用法示例
May 29 PHP
php中字符串和整数比较的操作方法
Jun 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
IStream与TStream之间的相互转换
2008/08/01 PHP
php数组合并的二种方法
2014/03/21 PHP
PHP用反撇号执行外部命令
2015/04/14 PHP
详解PHP的Yii框架中自带的前端资源包的使用
2016/03/31 PHP
PHP实现适用于自定义的验证码类
2016/06/15 PHP
JavaScript 存在陷阱 删除某一区域所有节点
2010/05/10 Javascript
NodeJS的模块写法入门(实例代码)
2012/03/07 NodeJs
JS脚本defer的作用示例介绍
2014/01/02 Javascript
js实现上传图片及时预览
2016/05/07 Javascript
原生js仿jquery animate动画效果
2016/07/13 Javascript
用nodejs的实现原理和搭建服务器(动态)
2016/08/10 NodeJs
JavaScript 函数模式详解及示例
2016/09/07 Javascript
jQuery.cookie.js使用方法及相关参数解释
2017/03/06 Javascript
jQuery实现倒计时功能 jQuery实现计时器功能
2017/09/19 jQuery
JavaScript代码执行的先后顺序问题
2017/10/29 Javascript
微信小程序三级联动选择器使用方法
2020/05/19 Javascript
jsonp格式前端发送和后台接受写法的代码详解
2019/11/07 Javascript
原生JS利用transform实现banner的无限滚动示例代码
2020/06/15 Javascript
vue实现匀速轮播效果
2020/06/29 Javascript
详解如何在Javascript中使用Object.freeze()
2020/10/18 Javascript
详解VUE中的插值( Interpolation)语法
2020/10/18 Javascript
python中os操作文件及文件路径实例汇总
2015/01/15 Python
Python抓取框架Scrapy爬虫入门:页面提取
2017/12/01 Python
Python面向对象类的继承实例详解
2018/06/27 Python
Pandas透视表(pivot_table)详解
2019/07/22 Python
Python 中的 import 机制之实现远程导入模块
2019/10/29 Python
线程安全及Python中的GIL原理分析
2019/10/29 Python
python中with语句结合上下文管理器操作详解
2019/12/19 Python
详解python常用命令行选项与环境变量
2020/02/20 Python
在ipython notebook中使用argparse方式
2020/04/20 Python
Python绘制词云图之可视化神器pyecharts的方法
2021/02/23 Python
详解CSS3中使用gradient实现渐变效果的方法
2015/08/18 HTML / CSS
StubHub西班牙:购买和出售全球活动门票
2017/06/05 全球购物
机械工程师求职自我评价
2013/09/23 职场文书
写自招自荐信的绝招!
2019/04/19 职场文书
详细介绍Next.js脚手架完整搭建封装
2022/04/26 Javascript