Swoole4.4协程抢占式调度器详解


Posted in PHP onMay 23, 2019

前言

Swoole内核团队开设的专栏,会逐渐投入精力写文章介绍Swoole的开发历程,实现原理,应用实践等,大家可以更好的交流,共同学习,建设PHP生态。

协程调度

去年Swoole推出了4.0版本后,完整的支持PHP协程,我们可以基于协程实现CSP编程,身边的开发者惊呼,原来PHP代码还可以这样写。Swoole的协程默认是基于IO调度,程序中有阻塞会自动让出当前协程,协程的各种优势我们不在这里展开讨论。如果是IO密集型的场景,可以表现得很不错。但是对于CPU密集型的场景,会导致一些协程因为得不到CPU时间片被饿死。

抢占式调度

我们在今年年初就计划实现Swoole的抢占式调度,以满足实现有些场景下的不均衡调度带来的问题。我们中间经历了几个版本,在这里和大家分享一下开发过程中的动机和解决办法。

Swoole4.4协程抢占式调度器详解

我们目的是为了均衡调度每个协程的CPU时间,比如协程3需要比较长的执行时间,我们必须把协程3的CPU时间主动中断,而不依赖IO事件,使得每个协程得到平均的执行时间。

起初,我们的想法是可以从PHP的循环中自动检测执行实践,若达到限制,可以自动让出当前协程。因为毕竟很少有人一马平川的写出占用很多CPU的代码,大都通过循环条件来控制。我们hook循环指令,每次执行循环指令的时候,都来检查协程的执行时间,我们很欣喜的得到了最初的版本。但是这样做比较hack,而且opcode经过opcache优化后,情况会变得有些复杂。

后来我们使用PHPticks机制,也就是在PHP代码编译期间,注入ticks指令,可以执行相应的函数,我们可以在这些函数中检测处理协程的时间,达到抢占式的效果,但是这里有一个问题,PHPdeclare(ticks=N)语法,只对当前脚本范围有效,也就是说项目稍微大点,require或者include进来的脚本,并不会自动注入ticks指令,这样Swoole开发者几乎是无法接受的。我们也试图给PHP官方提一个PR,可以在扩展层设置一个全局默认的ticks,但是官方不愿意采纳我们的提交,因为官方觉得这个功能对性能损耗比较大,而且有可能在PHP8移除这个功能。其实经过实测这个性能损耗并不大,而且我们已经在生产环境验证,并取得了显著的效果,即可以让出某些CPU密集的逻辑部分,使得服务整个相应时间更加均衡。

下图是我们生产环境一个RPC接口的调用端统计数据对比,客户端等待超时时间为2s,超时则统计为错误。

Swoole4.4协程抢占式调度器详解

左边一侧是没有抢占式调度,右侧是开了抢占式调度,可以发现,左侧总是会有偶尔超时情况,而经过优化之后,没有一个超时的请求,请求响应时间非常平滑,提升了服务的稳定性。

Swoole4.4协程抢占式调度器详解

可以从上图看出,由于抢占式调度的加入,去除了请求耗时高的毛刺,使得平均请求时间变得更加平滑,稳定。

想要做抢占式调度,对于PHP来说,有两个途径

  • 单线程的PHP的执行流,通过执行指令做文章,可以在PHP执行流程中注入逻辑,以检查执行时间,再加上Swoole的协程能力,可以在不同的协程中切换,以达到抢占CPU的目的。
  • 考虑开线程,负责检查当前执行协程执行时间。

经过以上办法的尝试,注入指令的路数基本是无法得到官方的支持,我们只能另谋出路,多开一个线程,只负责检查当前协程。具体的做法是,利用PHP-7.1.0引入的VM interrupt机制,默认每隔5ms检查一下当前协程是否达到最大执行时间,默认为10ms,如果超过,则让出当前协程,达到被其他协程抢占的目的。

示例代码

需要Swoole 4.4或更高版本
<?php
Co::set(['enable_preemptive_scheduler' => 1]);
$start = microtime(1);
echo "start\n";
$flag = 1;

go(function () use (&$flag) {
 echo "coro 1 start to loop\n";
 $i = 0;
 for (;;) {
  if (!$flag) {
   break;
  }
  $i++;
 }
 echo "coro 1 can exit\n";
});
 
$end = microtime(1);
$msec = ($end - $start) * 1000;
echo "use time $msec\n";
go(function () use (&$flag) {
 echo "coro 2 set flag = false\n";
 $flag = false;
});
echo "end\n";

执行结果

start
coro 1 start to loop
use time 11.121988296509
coro 2 set flag = false
end
coro 1 can exit

可以发现,代码逻辑可以从第一个协程的死循环中自动yield出来,执行第二个协程,如果没有这个特性,第二个协程永远不会被执行,导致被饿死。而这样做,第二个协程可以顺利被执行,最后执行结束后,第一个协程也会接着继续往下执行。达到我们的第二个协程主动抢占第一个协程CPU的效果。

这个特性在生产环境非常有用,尤其是对于实时系统或者响应时间比较敏感的场景。

最后

感谢大家对 Swoole 的长期支持和关注。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
无限级别菜单的实现
Oct 09 PHP
配置Apache2.2+PHP5+CakePHP1.2+MySQL5运行环境
Apr 25 PHP
windows下升级PHP到5.3.3的过程及注意事项
Oct 12 PHP
php和js如何通过json互相传递数据相关问题探讨
Feb 26 PHP
CodeIgniter框架URL路由总结
Sep 03 PHP
php实现的zip文件内容比较类
Sep 24 PHP
PHP 实现的将图片转换为TXT
Oct 21 PHP
Joomla数据库操作之JFactory::getDBO用法
May 05 PHP
浅谈PHP值mysql操作类
Jun 29 PHP
Yii2针对指定url的生成及图片等的引入方法小结
Jul 18 PHP
PHP单元测试框架PHPUnit用法详解
Jan 23 PHP
Laravel 自定命令以及生成文件的例子
Oct 23 PHP
PHP文件上传小程序 适合初学者学习!
May 23 #PHP
PHP观察者模式实例分析【对比JS观察者模式】
May 22 #PHP
PHP单例模式实例分析【防继承,防克隆操作】
May 22 #PHP
在Laravel5中正确设置文件权限的方法
May 22 #PHP
PHP 结合 Boostrap 结合 js 实现学生列表删除编辑及搜索功能
May 21 #PHP
thinkphp5框架API token身份验证功能示例
May 21 #PHP
php curl操作API接口类完整示例
May 21 #PHP
You might like
PHP使用GIFEncoder类生成gif动态滚动字幕
2014/07/01 PHP
PHP数组式访问接口ArrayAccess用法分析
2017/12/28 PHP
php实现文件上传基本验证
2020/03/04 PHP
js判断设备是否为PC并调整图片大小
2014/02/12 Javascript
javascript折半查找详解
2015/01/26 Javascript
JavaScript实现重置表单(reset)的方法
2015/04/02 Javascript
js实现鼠标滑过文字链接色彩变化的效果
2015/05/06 Javascript
基于jquery实现瀑布流布局
2020/06/28 Javascript
JS中的数组方法笔记整理
2016/07/26 Javascript
html判断当前页面是否在iframe中的实例
2016/11/30 Javascript
ReactNative页面跳转Navigator实现的示例代码
2017/08/02 Javascript
react-native-tab-navigator组件的基本使用示例代码
2017/09/07 Javascript
elementUI Vue 单个按钮显示和隐藏的变换功能(两种方法)
2018/09/04 Javascript
Node.js中package.json中库的版本号(~和^)
2019/04/02 Javascript
原生js实现each方法实例代码详解
2019/05/27 Javascript
es6中new.target的作用和使用场景简单示例分析
2020/03/14 Javascript
微信小程序中data-key属性之数据传输(经验总结)
2020/08/22 Javascript
JavaScript 中判断变量是否为数字的示例代码
2020/10/22 Javascript
ant-design-vue中的select选择器,对输入值的进行筛选操作
2020/10/24 Javascript
vue+element UI实现树形表格
2020/12/29 Vue.js
[58:59]完美世界DOTA2联赛PWL S3 access vs CPG 第一场 12.13
2020/12/16 DOTA
详解Python实现按任意键继续/退出的功能
2016/08/19 Python
python 基本数据类型占用内存空间大小的实例
2018/06/12 Python
如何基于Python + requests实现发送HTTP请求
2020/01/13 Python
Python函数必须先定义,后调用说明(函数调用函数例外)
2020/06/02 Python
完美解决ARIMA模型中plot_acf画不出图的问题
2020/06/04 Python
使用html5 canvas 画时钟代码实例分享
2015/11/11 HTML / CSS
欧洲高端品牌直销店:Fashionesta
2016/08/31 全球购物
缅甸网上购物:Shop.com.mm
2017/12/05 全球购物
大学生个人总结的自我评价
2013/10/05 职场文书
高中生家长会演讲稿
2014/01/14 职场文书
骨干教师培训方案
2014/05/06 职场文书
寒假社会实践个人总结
2015/03/06 职场文书
详解NodeJS模块化
2021/06/15 NodeJs
《最终幻想14》6.01版本4月5日推出 追加新任务新道具
2022/04/03 其他游戏
python疲劳驾驶困倦低头检测功能的实现
2022/04/04 Python