/**咱们就以上面的示例代码为例,说一说协程的执行流程。
只有在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代码
如果后端在设定的超时时间内,未能回包,返回false
client的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();
协程的执行流程
%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2017-03-01-%e4%b8%8a%e5%8d%8811-02-55
[sequence]
主进程->回调函数: 嘿,有数据到了,麻烦你处理下
Note right of 回调函数: 遇到异步IO
回调函数-->主进程: 已保存状态,你执行其他任务吧
Note left of 主进程: 去执行其他任务
主进程->回调函数: 发送的数据,有消息返回啦,你处理下
[/sequence]
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();
}
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;
}
sw_inline void coro_yield()
{
SWOOLE_GET_TSRMLS;
// 还原栈信息
#if PHP_MAJOR_VERSION >= 7
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;
#else
EG(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 yield
coro_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;
}
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));
}
本文为 @ 21CTO 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。