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 相关文章推荐
PHP中的加密功能
Oct 09 PHP
PHP的FTP学习(四)
Oct 09 PHP
php intval的测试代码发现问题
Jul 27 PHP
php url地址栏传中文乱码解决方法集合
Jun 25 PHP
php站内搜索并高亮显示关键字的实现代码
Dec 29 PHP
php cURL和Rolling cURL并发方式比较
Oct 30 PHP
PHP中使用CURL模拟登录并获取数据实例
Jul 01 PHP
深入浅析用PHP实现MVC
Mar 02 PHP
微信支付开发动态链接Native支付
Jul 12 PHP
PHP共享内存使用与信号控制实例分析
May 09 PHP
PHP图像处理技术实例总结【绘图、水印、验证码、图像压缩】
Dec 08 PHP
PHP5.5新特性之yield理解与用法实例分析
Jan 11 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
一个简单实现多条件查询的例子
2006/10/09 PHP
56.com视频采集接口程序(PHP)
2007/09/22 PHP
Yii框架中用response保存cookie,用request读取cookie的原理解析
2019/09/04 PHP
使用Rancher在K8S上部署高性能PHP应用程序的教程
2020/07/10 PHP
千分位数字格式化(用逗号隔开 代码已做了修改 支持0-9位逗号隔开)的JS代码
2013/12/05 Javascript
nodejs教程之制作一个简单的文章发布系统
2014/11/21 NodeJs
js完美解决IE6不支持position:fixed的bug
2015/04/24 Javascript
jQuery mobile转换url地址及获取url中目录部分的方法
2015/12/04 Javascript
jQuery遍历DOM元素与节点方法详解
2016/04/14 Javascript
Angular 2 利用Router事件和Title实现动态页面标题的方法
2017/08/23 Javascript
Vue单页应用引用单独的样式文件的两种方式
2018/03/30 Javascript
JavaScript实现的前端AES加密解密功能【基于CryptoJS】
2018/08/28 Javascript
Node.js 的 GC 机制详解
2019/06/03 Javascript
JavaScript数组排序小程序实现解析
2020/01/13 Javascript
解决三元运算符 报错“SyntaxError: can''t assign to conditional expression”
2020/02/12 Javascript
vue中element 的upload组件发送请求给后端操作
2020/09/07 Javascript
[02:45]DOTA2英雄基础教程 伐木机
2013/12/23 DOTA
python正则匹配查询港澳通行证办理进度示例分享
2013/12/27 Python
python删除特定文件的方法
2015/07/30 Python
在Python程序和Flask框架中使用SQLAlchemy的教程
2016/06/06 Python
Python中easy_install 和 pip 的安装及使用
2017/06/05 Python
Python及Django框架生成二维码的方法分析
2018/01/31 Python
python 时间信息“2018-02-04 18:23:35“ 解析成字典形式的结果代码详解
2018/04/19 Python
使用python 3实现发送邮件功能
2018/06/15 Python
Python开发的十个小贴士和技巧及长常犯错误
2018/09/27 Python
使用PyInstaller将Pygame库编写的小游戏程序打包为exe文件及出现问题解决方法
2019/09/06 Python
python自动结束mysql慢查询会话的实例代码
2019/10/27 Python
pytorch 改变tensor尺寸的实现
2020/01/03 Python
解决Python图形界面中设置尺寸的问题
2020/03/05 Python
受希腊女神灵感的晚礼服、鸡尾酒礼服和婚纱:THEIA
2018/04/15 全球购物
英国知名小木屋定制网站:Tiger Sheds
2020/03/06 全球购物
商务英语专业自荐信
2013/10/14 职场文书
读书心得体会
2013/12/28 职场文书
学习雷锋做美德少年寄语大全
2014/04/09 职场文书
买卖车协议书
2014/04/21 职场文书
党员带头倡议书
2015/04/29 职场文书