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+Tidy-完美的XHTML纠错+过滤
Apr 10 PHP
linux系统上支持php的 iconv()函数的方法
Oct 01 PHP
destoon二次开发常用数据库操作
Jun 21 PHP
php中ftp_chdir与ftp_cdup函数用法
Nov 18 PHP
Thinkphp搜索时首页分页和搜索页保持条件分页的方法
Dec 05 PHP
php采集内容中带有图片地址的远程图片并保存的方法
Jan 03 PHP
Win10 下安装配置IIS + MySQL + nginx + php7.1.7
Aug 04 PHP
php二维码生成以及下载实现
Sep 28 PHP
PHP实现websocket通信的方法示例
Aug 28 PHP
laravel实现Auth认证,登录、注册后的页面回跳方法
Sep 30 PHP
PHP实现发送微博消息功能完整示例
Dec 04 PHP
laravel5.6框架操作数据curd写法(查询构建器)实例分析
Jan 26 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
ThinkPHP3.1基础知识快速入门
2014/06/19 PHP
jQuery 操作option的实现代码
2011/03/03 Javascript
使用控制台破解百小度一个月只准改一次名字
2015/08/13 Javascript
浅析js中substring和substr的方法
2015/11/09 Javascript
NodeJS实现阿里大鱼短信通知发送
2016/01/17 NodeJs
全面介绍javascript实用技巧及单竖杠
2016/07/18 Javascript
bootstrap table操作技巧分享
2017/02/15 Javascript
详解利用jsx写vue组件的方法示例
2017/07/17 Javascript
利用three.js画一个3D立体的正方体示例代码
2017/11/19 Javascript
浅谈VUE监听窗口变化事件的问题
2018/02/24 Javascript
vue兄弟组件传递数据的实例
2018/09/06 Javascript
微信实现自动跳转到用其他浏览器打开指定APP下载
2019/02/15 Javascript
详解微信小程序胶囊按钮返回|首页自定义导航栏功能
2019/06/14 Javascript
如何给element添加一个抽屉组件的方法步骤
2019/07/14 Javascript
JS addEventListener()和attachEvent()方法实现注册事件
2021/01/11 Javascript
[06:37]2014DOTA2国际邀请赛 昔日王者渴望重回巅峰
2014/07/12 DOTA
python使用xmlrpclib模块实现对百度google的ping功能
2015/06/02 Python
Python写入数据到MP3文件中的方法
2015/07/10 Python
利用python将xml文件解析成html文件的实现方法
2017/12/22 Python
浅谈python中字典append 到list 后值的改变问题
2018/05/04 Python
Python判断一个list中是否包含另一个list全部元素的方法分析
2018/12/24 Python
Django基础知识 URL路由系统详解
2019/07/18 Python
python3.6中@property装饰器的使用方法示例
2019/08/17 Python
Python实现代码块儿折叠
2020/04/15 Python
HUGO BOSS美国官方网上商店:世界知名奢侈品牌
2017/08/04 全球购物
Tretorn美国官网:瑞典外套和鞋类品牌,抵御风雨
2018/07/19 全球购物
龟牌英国商店:Turtle Wax Brand Store UK
2019/07/02 全球购物
JBL美国官方商店:扬声器、耳机等
2019/12/01 全球购物
Deichmann英国:德国鞋类零售商
2021/01/30 全球购物
青年创业培训欢迎词
2014/01/08 职场文书
大学学习生活感言
2014/01/18 职场文书
俞敏洪北大演讲稿
2014/05/22 职场文书
高三霸气励志标语
2014/06/24 职场文书
幼儿园教师读书笔记
2015/06/29 职场文书
2016公务员年度考核评语
2015/12/01 职场文书
高通2023 年将发布高性能PC处理器
2022/04/29 数码科技