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 相关文章推荐
5.PHP的其他功能
Oct 09 PHP
几个学习PHP的网址
Nov 25 PHP
php Memcache 中实现消息队列
Nov 24 PHP
PHP 的异常处理、错误的抛出及回调函数等面向对象的错误处理方法
Dec 07 PHP
PHP批量删除、清除UTF-8文件BOM头的代码实例
Apr 14 PHP
php实现用于验证所有类型的信用卡类
Mar 24 PHP
PHP利用hash冲突漏洞进行DDoS攻击的方法分析
Mar 26 PHP
php实现读取内存顺序号
Mar 29 PHP
浅谈Yii乐观锁的使用及原理
Jul 25 PHP
thinkphp ajaxfileupload实现异步上传图片的示例
Aug 28 PHP
浅谈Laravel模板实体转义带来的坑
Oct 22 PHP
php实现根据身份证获取精准年龄
Feb 26 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
虹吸壶是谁发明的?煮出来的咖啡好喝吗
2021/03/04 冲泡冲煮
用PHP实现文件上传二法
2006/10/09 PHP
PHP5 字符串处理函数大全
2010/03/23 PHP
php5.2 Json不能正确处理中文、GB编码的解决方法
2014/03/28 PHP
使用pthreads实现真正的PHP多线程(需PHP5.3以上版本)
2014/05/05 PHP
9条PHP编程小知识及易犯的小错误
2015/01/22 PHP
基于jQuery捕获超链接事件进行局部刷新代码
2012/05/10 Javascript
jQuery 插件仿百度搜索框智能提示(带Value值)
2013/01/22 Javascript
node.js中的querystring.stringify方法使用说明
2014/12/10 Javascript
jQuery使用addClass()方法给元素添加多个class样式
2015/03/26 Javascript
javascript等号运算符使用详解
2015/04/16 Javascript
轻松学习jQuery插件EasyUI EasyUI实现树形网络基本操作(2)
2015/11/30 Javascript
JavaScript中利用jQuery绑定事件的几种方式小结
2016/03/06 Javascript
如何使用jquery修改css中带有!important的样式属性
2016/04/28 Javascript
jQuery自适应轮播图插件Swiper用法示例
2016/08/24 Javascript
原生js编写基于面向对象的分页组件
2016/12/05 Javascript
js控制文本框禁止输入特殊字符详解
2017/04/07 Javascript
微信小程序实现的一键拨号功能示例
2019/04/24 Javascript
Vue的路由及路由钩子函数的实现
2019/07/02 Javascript
解决Vue-Router升级导致的Uncaught (in promise)问题
2020/08/07 Javascript
[52:32]完美世界DOTA2联赛PWL S2 Magma vs LBZS 第三场 11.18
2020/11/18 DOTA
Web服务器框架 Tornado简介
2014/07/16 Python
Python获取某一天是星期几的方法示例
2017/01/17 Python
python中使用正则表达式的后向搜索肯定模式(推荐)
2017/11/11 Python
简单实现python收发邮件功能
2018/01/05 Python
Python搭建代理IP池实现检测IP的方法
2019/10/27 Python
wxPython实现绘图小例子
2019/11/19 Python
Python类型转换的魔术方法详解
2020/12/23 Python
aec加密 php_php aes加密解密类(兼容php5、php7)
2021/03/14 PHP
CSS3的Border-radius轻松制作圆角
2012/12/24 HTML / CSS
家庭户外服装:Hawkshead
2017/11/02 全球购物
goodhealth官方海外旗舰店:新西兰国民营养师
2017/12/15 全球购物
财务工作个人总结
2015/02/27 职场文书
大学生预备党员自我评价
2015/03/04 职场文书
2016七夕情人节广告语
2016/01/28 职场文书
《假如》教学反思
2016/02/17 职场文书