聊聊Swoole2.0协程

Swoole 2.0正式版发布了。2.0版本最大的更新是增加了对协程(Coroutine)的支持。正式版已同时支持PHP5和PHP7。基于Swoole2.0协程PHP开发者可以已同步的方式编写代码,底层自动进行协程调度,转变为异步IO。解决了传统异步编程嵌套回调的问题。

目前Swoole底层内置的协程客户端组件包括:udpclient、tcpclient、httpclient、redisclient、mysqlclient,基本涵盖了开发者常用的几种通信协议。协程组件只能在服务器的onConnect、onRequest、onReceive、onMessage 回调函数中使用。

注意,Swoole 2.0.5以前的版本还是灰度测试版本,可能会存在问题。 beta是因为协程是全新的版本。


协程的使用示例

/**只有在Server中才能使用协程。包括 http server,websocket server 和 server。
*/
$server = new Swoole\Http\Server('127.0.0.1', 9501);/**触发on request事件时,SWOOLE会开辟一个协程栈,对协程栈进行初始化*/
$server->on('Request', function ($request, $response) {$tcp_cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);/**client在调用connect函数后,SWOOLE会将PHP上下文信息保存到当前栈内然后将协程挂起,待确认连接成功后,触发epoll事件,然后协程切换恢复PHP上下文信息,返回结果,继续执行PHP代码*/if ($tcp_cli->connect('127.0.0.1', 9906) === false) {$response->end("connect server failed.");return;}$tcp_cli->send('test for the coro');/**client在调用recv函数后,SWOOLE会将PHP上下文信息保存到当前栈内然后将协程挂起待后端svr回包,触发epoll事件,然后协程切换恢复PHP上下文信息,返回结果,继续执行PHP代码如果后端在设定的超时时间内,未能回包,返回falseclient的errCode定为110*/$ret = $tcp_cli->recv(100);$tcp_cli->close();if ($ret) {$response->end(" swoole response is ok");} else {$response->end(" recv failed error : {$tcp_cli->errCode}");}
});$server->start();

协程的执行流程

主进程->回调函数: 嘿,有数据到了,麻烦你处理下
Note right of 回调函数: 遇到异步IO
回调函数-->主进程: 已保存状态,你执行其他任务吧
Note left of 主进程: 去执行其他任务
主进程->回调函数: 发送的数据,有消息返回啦,你处理下

咱们就以上面的示例代码为例,说一说协程的执行流程。
Http Server监听9051端口。当有相关事件发生时,如有数据到达,就会执行绑定到Request上的回调函数。在执行回调函数之前,会创建一个协程。这时,会保存CPU寄存器的状态和ZendVM Stack信息。
在回调函数执行过程中,如果遇到IO操作,如$tcp_cli->connect(,就会保存当前的状态,并让出CPU使用权。当前请求执行被挂起。
让CPU出使用权后,CPU就可以用于处理其他事件。如处理其他客户端的Request请求。
当被挂起的请求,又有新的事件发生,如上面$tcp_cli->connect()的数据已经返回。这时,会使用挂起前保存的状态信息恢复,然后继续执行回调函数。
如果在执行过程中,再次遇到IO操作,会继续执行保存状态和让出CPU使用权。

协程的意义

这些IO操作都是非阻塞的,即发送请求和获取数据分为两步。当请求发送完毕后,就会进行状态保存和让出CPU使用权。在等待请求数据返的这段时间,CPU可以执行一些其他程序。这样就可以充分利用CPU。

协程的实现

Swoole的协程是基于 setjmp 、 longjmp 实现的。Swoole为每个协程都分配了空间,用于保存协程切换时的状态信息。进行协程切换时会自动保存Zend VM的内存状态(主要是EG全局内存和vm stack)。当回调函数执行完毕后,会自动销毁分配的空间。

创建协程

什么时候会创建协程?在Server的onConnect、onRequest、onReceive、onMessage 回调函数被执行前会创建一个协程。
协程创建的方法是coro_create。相关源码可以查看swoole_coroutine.c文件。
coro_create方法中主要进行了如下操作:

int sw_coro_create(zend_fcall_info_cache *fci_cache, zval **argv, int argc, zval **retval, void *post_callback, void* params)
{// 为回调函数的执行做一些准备工作.......COROG.require = 1;// 使用setjmp开启一个协程if (!setjmp(*swReactorCheckPoint)){// setjmp第一次调用会进入此代码分支,执行回调函数zend_execute_ex(execute_data TSRMLS_CC);......// 执行完毕后,关闭协程coro_close(TSRMLS_C);......coro_status = CORO_END;}else{/**如果执行longjump,会调到上面的setjmp(*swReactorCheckPoint)行。但是,setjmp的返回值为非0。因此,longjump后,会进入此代码分支。让出CPU执行权。*/coro_status = CORO_YIELD;}COROG.require = 0;return coro_status;
}

协程让出CPU执行权yield

什么时候会让出CPU执行权?当回调函数中遇到异步IO的时候,会让出CPU执行权。如,代码中的connect操作。下面,我们就以connect操作为例,看看让出CPU执行权时都做了那些操作。
connect的相关代码在swoole_coroutine.c文件中。代码如下:

static PHP_METHOD(swoole_client_coro, connect)
{long port = 0, sock_flag = 0;......//nonblock async// 发送连接数据,无需等待对方返回数据,就执行下面代码if (cli->connect(cli, host, port, timeout, sock_flag) < 0){......}......// 获取一个内存空间,用于保存当前执行的上下文信息。php_context *sw_current_context = swoole_get_property(getThis(), 0);......// 保存协程信息coro_save(sw_current_context);// 让出CPU使用权coro_yield();
}

保存协程信息

所谓的协程信息主要就是当前的上下文执行信息。coro_save方法在swoole_coroutine.c文件中。代码如下:

sw_inline php_context *sw_coro_save(zval *return_value, php_context *sw_current_context)
{// 下面的代码主要是把当前的执行状态保存到之前获取的内存空间中zend_execute_data *current = EG(current_execute_data);if (ZEND_CALL_INFO(current) & ZEND_CALL_RELEASE_THIS){zval_ptr_dtor(&(current->This));}zend_vm_stack_free_args(EG(current_execute_data));zend_vm_stack_free_call_frame(EG(current_execute_data));strncpy(SWCC(uid), COROG.uid, 20);SWCC(current_coro_return_value_ptr) = return_value;SWCC(current_execute_data) = EG(current_execute_data)->prev_execute_data;SWCC(current_vm_stack) = EG(vm_stack);SWCC(current_vm_stack_top) = EG(vm_stack_top);SWCC(current_vm_stack_end) = EG(vm_stack_end);SWCC(current_task) = COROG.current_coro;SWCC(allocated_return_value_ptr) = COROG.allocated_return_value_ptr;return sw_current_context;}

让出CPU执行权

coro_yield方法的作用是让出CPU执行权。代码在swoole_coroutine.c文件中。

sw_inline void coro_yield()
{SWOOLE_GET_TSRMLS;
// 还原栈信息
#if PHP_MAJOR_VERSION >= 7EG(vm_stack) = COROG.origin_vm_stack;EG(vm_stack_top) = COROG.origin_vm_stack_top;EG(vm_stack_end) = COROG.origin_vm_stack_end;
#elseEG(argument_stack) = COROG.origin_vm_stack;EG(current_execute_data) = COROG.origin_ex;
#endif// 跳转到coro_create方法中setjmp代码行。longjmp(*swReactorCheckPoint, 1);
}

在这个方法中主要进行了还原栈信息和longjump操作。
COROG.origin_vm_stack 这些栈信息的初始化在coro_init方法中。记录了协程执行前的状态。

恢复协程

当异步IO有数据返回后,会进行协程恢复。协程恢复的方法是coro_resume。在swoole_coroutine.c文件中。代码如下:

int sw_coro_resume(php_context *sw_current_context, zval *retval, zval *coro_retval)
{// 使用之前保存的协程信息恢复执行上下文环境。EG(vm_stack) = SWCC(current_vm_stack);....int coro_status;// 设置跳转点,方便在执行过程中再遇到异步IO操作,进行跳转。if (!setjmp(*swReactorCheckPoint)){//coro exit// 继续执行回调函数zend_execute_ex(sw_current_context->current_execute_data TSRMLS_CC);coro_close(TSRMLS_C);coro_status = CORO_END;}else{//coro yieldcoro_status = CORO_YIELD;}if (unlikely(coro_status == CORO_END && EG(exception))){sw_zval_ptr_dtor(&retval);zend_exception_error(EG(exception), E_ERROR TSRMLS_CC);}return coro_status;
}

可见,创建协程和恢复协程的整体代码结构差不多。

结束协程

当回到函数执行完毕后,会结束协程。
coro_close方法用于结束协程。源码在swoole_coroutine.c文件中。

sw_inline void coro_close(TSRMLS_D)
{// 释放为协程而申请的相关资源efree(EG(vm_stack));efree(COROG.allocated_return_value_ptr);// 恢复执行栈EG(vm_stack) = COROG.origin_vm_stack;EG(vm_stack_top) = COROG.origin_vm_stack_top;EG(vm_stack_end) = COROG.origin_vm_stack_end;--COROG.coro_num;swTrace("closing coro and %d remained. usage size: %zu. malloc size: %zu", COROG.coro_num, zend_memory_usage(0), zend_memory_usage(1));
}

以swoole为例,学习如何实现协程相关推荐

  1. 以爬虫为例,单线程,协程,线程,进程之间性能的比较,原来协程可以这么快?

    前言 因为刚刚学习到了协程,然后之前也对爬虫有一定的了解,所以打算结合之前学的线程和进程,和协程进行对比,看看它的性能到底有多高,在测试完成后,结果还是不错的!下面就直接上代码了,因为代码逻辑都比较简 ...

  2. Kotlin学习——简单运用协程网络下载图片并更新到UI

    kotlin学习 协程Coroutines学习 简单小Demo:通过协程下载一张网络图片并显示出来 文章目录 kotlin学习 前言 一.如何开启一个协程? 二.如何在项目中使用协程 增加对 Kotl ...

  3. mysql逻辑架构连接池_GitHub - zzjzzb/ycsocket: 基于swoole的socket框架,支持协程版MySQL、Redis连接池、Actor模型...

    ycsocket 基于 swoole 和 swoole_orm 的 websocket 框架,各位可以自己扩展到 TCP/UDP,HTTP. 在ycsocket 中,采用的是全协程化,全池化的数据库. ...

  4. Kotlin学习笔记24 协程part4 协程的取消与超时

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 1 如何取消协程 import kotlinx.coroutines.*/*** 协程的取消*/fun main() = ...

  5. 学习|Unity3D使用协程实现减速停车效果

    学更好的别人, 做更好的自己. --<微卡智享> 本文长度为4303字,预计阅读11分钟 前言 上一篇<学习|Unity3d的导航实现循环线路移动>讲了一下定制循环行驶路线的方 ...

  6. lua学习笔记之协程

    1.基础 所有协程相关的在表coroutine中,创建通过create来创建,参数为函数,返回值类型为thread. 协程状态有:suspended, running, normal, dead.通过 ...

  7. Go语言学习笔记 - PART13 - 协程与通道

    协程 应用程序处理并发的部分 协程跟操作系统的线程之间不是一对一的关系 协程是独立执行的,它们之间必须同行才会变得有用 通道 负责协程之间的通信,从而避免所有由共享内存导致的陷阱 通道只能传输一种类型 ...

  8. Kotlin学习笔记26 协程part6 协程与线程的关系 Dispatchers.Unconfined 协程调试 协程上下文切换 Job详解 父子协程的关系

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 1 协程与线程的关系 import kotlinx.coroutines.* import java.util.concu ...

  9. Kotlin学习笔记25 协程part5 协程的同步与异步

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 1 程序运行时间统计measureTimeMillis /*** 程序运行时间统计measureTimeMillis** ...

  10. Kotlin学习笔记23 协程part3 lambda表达式深入 挂起函数 全局协程

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 1 lambda表达式深入 /*** lambda 表达式深入* 当函数参数是函数时 并且该函数只有一个参数 可以不传入任 ...

最新文章

  1. 61种常用JavaScript
  2. vs找不到dll_零基础学习Python_绝对VS相对
  3. 关于c语言的符号常量以下叙述中正确的是,关于C语言的符号常量,以下叙述中正确的是( )...
  4. Custom Looks using Qt Style Sheets
  5. htmlentities和htmlspecialchars 的区别
  6. linux入门目录汇总
  7. 迭代法求根c语言程序对数方程,【实验一】方程求根:牛顿迭代法
  8. Angular在页面加载很慢的时候,会出现双花括号的问题
  9. 现代教育技术没有计算机基础知识教程,浅析现代教育技术在专科计算机基础课教学中的应用...
  10. win10、ThinkPad遇到静音键不亮,但可以使用的问题
  11. 一则晦涩难懂的码农段子 | 每日趣闻
  12. Vue 路由导航守卫(全局守卫、路由独享守卫、组件内守卫)详解
  13. ERROR 1130 (HY000): Host ‘192.168.78.128‘ is not allowed to connect to this MySQL server
  14. Boston_House_Price经典例子分析
  15. 剑走偏锋--使用WMI获取远程计算机进程程序集中查毒病毒打造内网安全环境
  16. SQL语言的数据查询之嵌套查询
  17. 我们逃离北上广,美国人逃离硅谷
  18. 收藏(Flash欣赏类)
  19. 常用的几种布局方式---Flex 布局(垂直居中展示)
  20. 2022年低压电工新版试题及低压电工试题及解析

热门文章

  1. python render_python:玫瑰图展示2020年1-3月轿车销量前十及占比(pyecharts)
  2. php怎么跟小程序链接,小程序的链接怎么提取?
  3. scala 返回值_Scala系列3-Scala函数
  4. python生产者消费者模式_【整理】Python生产者消费者模型代码 | 勤奋的小青蛙
  5. Windows下Nginx负载均衡配置和优化方案
  6. 网络不稳定的原因_原来网络不稳定,也有可能是这个原因造成的!
  7. 圆弧裁剪算法c++_程序媛转TA之理论篇十二:反走样的几种算法原理
  8. php请求图片,PHP - 发送GET请求并获取图片作为回报
  9. python查找字符串出现次数_python实现从字符串中找出字符1的位置以及个数的方法...
  10. 数据结构(二):线性表的使用原则以及链表的应用-稀疏矩阵的三元组表示