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 相关文章推荐
BBS(php &amp; mysql)完整版(八)
Oct 09 PHP
PHP如何得到当前页和上一页的地址?
Nov 27 PHP
mysql 的 like 问题,超强毕杀记!!!
Jan 18 PHP
php判断变量类型常用方法
Apr 24 PHP
PHP中文分词 自动获取关键词介绍
Nov 13 PHP
php+mysql数据库查询实例
Jan 21 PHP
php实现的网络相册图片防盗链完美破解方法
Jul 01 PHP
php过滤所有的空白字符(空格、全角空格、换行等)
Oct 27 PHP
Yii2 rbac权限控制操作步骤实例教程
Apr 29 PHP
php使用正则表达式获取字符串中的URL
Dec 29 PHP
PHP 文件上传限制问题
Sep 01 PHP
laravel 框架执行流程与原理简单分析
Feb 01 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
通过ODBC连接的SQL SERVER实例
2006/10/09 PHP
PHP 危险函数全解析
2009/09/09 PHP
php+Ajax处理xml与json格式数据的方法示例
2019/03/04 PHP
asp.net和php的区别点总结
2019/10/10 PHP
Js 刷新框架页的代码
2010/04/13 Javascript
jquery.validate分组验证代码
2011/03/17 Javascript
你的 mixin 真的兼容 ECMAScript 5 吗?
2013/04/11 Javascript
使用jquery修改表单的提交地址基本思路
2014/06/04 Javascript
使用Sticky组件实现带sticky效果的tab导航和滚动导航的方法
2016/03/22 Javascript
JS区分浏览器页面是刷新还是关闭
2016/04/17 Javascript
深入理解ECMAScript的几个关键语句
2016/06/01 Javascript
jQuery Password Validation密码验证
2016/12/30 Javascript
Javascript 链式作用域详细介绍
2017/02/23 Javascript
详解nodejs模板引擎制作
2017/06/14 NodeJs
详解vue-cli 快速搭建单页应用之遇到的问题及解决办法
2018/03/01 Javascript
jQuery插件Validation表单验证详解
2018/05/26 jQuery
JavaScript引用类型Date常见用法实例分析
2018/08/08 Javascript
在Vue项目中,防止页面被缩放和放大示例
2019/10/28 Javascript
[56:45]DOTA2上海特级锦标赛D组小组赛#1 EG VS COL第一局
2016/02/28 DOTA
Python中将字典转换为XML以及相关的命名空间解析
2015/10/15 Python
python中subprocess批量执行linux命令
2018/04/27 Python
pyspark 读取csv文件创建DataFrame的两种方法
2018/06/07 Python
Python 忽略warning的输出方法
2018/10/18 Python
Python3.6.2调用ffmpeg的方法
2019/01/10 Python
利用Python对文件夹下图片数据进行批量改名的代码实例
2019/02/21 Python
python基于json文件实现的gearman任务自动重启代码实例
2019/08/13 Python
pytorch 在网络中添加可训练参数,修改预训练权重文件的方法
2019/08/17 Python
pycharm如何实现跨目录调用文件
2020/02/28 Python
详解如何在登录过期后跳出Ifram框架
2020/09/10 HTML / CSS
JavaScript获取当前url根目录(路径)
2014/02/19 面试题
读书月活动方案
2014/05/22 职场文书
学习雷锋月活动总结
2014/07/03 职场文书
餐饮周年庆活动方案
2014/08/14 职场文书
预备党员自我评价范文
2015/03/04 职场文书
vue实现移动端div拖动效果
2022/03/03 Vue.js
分享node.js实现简单登录注册的具体代码
2022/04/26 NodeJs