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实现多服务器session共享之NFS共享的方法
Mar 16 PHP
php中比较简单的导入phpmyadmin生成的sql文件的方法
Jun 28 PHP
PHP下打开phpMyAdmin出现403错误的问题解决方法
May 23 PHP
探讨PHP中OO之静态关键字以及类常量的详解
Jun 07 PHP
新浪SAE云平台下使用codeigniter的数据库配置
Jun 12 PHP
PHP的拦截器实例分析
Nov 03 PHP
PHP生成随机密码方法汇总
Aug 27 PHP
php多线程并发实现方法
Sep 30 PHP
Thinkphp 中 distinct 的用法解析
Dec 14 PHP
yii使用bootstrap分页样式的实例
Jan 17 PHP
php常用字符串长度函数strlen()与mb_strlen()用法实例分析
Jun 25 PHP
Yii框架的布局文件实例分析
Sep 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
php实现搜索类封装示例
2016/03/31 PHP
PHP魔术方法之__call与__callStatic使用方法
2017/07/23 PHP
Ajax请求PHP后台接口返回信息的实例代码
2018/08/21 PHP
获取Javscript执行函数名称的方法
2006/12/22 Javascript
Javascript select下拉框操作常用方法
2009/11/09 Javascript
javascript控制frame,iframe的src属性代码
2009/12/31 Javascript
Javascript Throttle &amp; Debounce应用介绍
2013/03/19 Javascript
jQuery实现简单下拉导航效果
2015/09/07 Javascript
基于BootStrap Metronic开发框架经验小结【四】Bootstrap图标的提取和利用
2016/05/12 Javascript
JS函数arguments数组获得实际传参数个数的实现方法
2016/05/28 Javascript
AngularJS入门教程之路由与多视图详解
2016/08/19 Javascript
AngularJS递归指令实现Tree View效果示例
2016/11/07 Javascript
JS前端加密算法示例
2016/12/22 Javascript
JavaScript反弹动画效果的实现代码
2017/07/13 Javascript
Nodejs实现用户注册功能
2019/04/14 NodeJs
JQuery获取元素尺寸、位置及页面滚动事件应用示例
2019/05/14 jQuery
vue-week-picker实现支持按周切换的日历
2019/06/26 Javascript
python实现删除文件与目录的方法
2014/11/10 Python
Python编程判断一个正整数是否为素数的方法
2017/04/14 Python
python爬虫入门教程--利用requests构建知乎API(三)
2017/05/25 Python
微信小程序跳一跳游戏 python脚本跳一跳刷高分技巧
2018/01/04 Python
Python检测数据类型的方法总结
2019/05/20 Python
简单了解python元组tuple相关原理
2019/12/02 Python
python 爬虫 实现增量去重和定时爬取实例
2020/02/28 Python
Python常用类型转换实现代码实例
2020/07/28 Python
Python 删除List元素的三种方法remove、pop、del
2020/11/16 Python
一款超酷的js+css3实现的3D标签云特效兼容ie7/8/9
2013/11/18 HTML / CSS
李维斯德国官方网上商店:Levi’s德国
2016/09/10 全球购物
娇韵诗香港官网:Clarins香港
2020/08/13 全球购物
介绍一下linux的文件系统
2015/10/06 面试题
优秀少先队工作者事迹材料
2014/05/13 职场文书
关于青春的演讲稿500字
2014/08/22 职场文书
实习指导老师意见
2015/06/04 职场文书
歌咏比赛主持词
2015/06/29 职场文书
导游词之阳朔遇龙河
2019/12/16 职场文书
面试必问:圣杯布局和双飞翼布局的区别
2021/05/13 HTML / CSS