上一篇文章,我们分析了Swoole PHP
协程入口函数swoole::PHPCoroutine::main_func
的zend_first_try
和zend_catch
,明白了这对结构解决了什么问题。这篇文章,我们继续分析协程入口函数。
我们使用的PHP
版本是7.3.12
,Swoole
的版本是v4.4.16
。我们把commit
切到v4.4.16
:
1 | git checkout v4.4.16 |
我们继续读代码:
1 | int i; |
其中:
1 | php_coro_args *php_arg = (php_coro_args *) arg; |
其中,
fci_cache
这个结构是由我们在函数Coroutine::create
中传递进去的函数生成的。例如:
1 | Coroutine::create(function () { |
就是由这个匿名函数生成的。
func
则是对应这个匿名函数本体。
argv
则对应着我们传递给函数的参数,argc
则是参数的个数。例如:
1 |
|
那么argv[0]
存储的就是这里的整形参数1
,argv[1]
存储的就是这里的字符串参数arg2
。对应的,argc
就等于2
。
_retval
则是用来保存函数的返回值。注意,这里保存的不是Coroutine::create
这个函数的返回值,而是传递给Coroutine::create
的函数的返回值(我们把传递进去的函数叫做协程任务函数吧)。
举个例子:
1 |
|
此时,_retval
存储的就是字符串ret
。
我们继续读代码:
1 | if (fci_cache.object) |
这段代码解决了什么问题呢?
协程任务函数是属于某个对象的话,那么需要给这个对象加引用计数,不然协程发生切换时,
PHP
会默认释放掉这个对象,导致下次协程切换回来发生错误。
我们编写一下测试脚本:
1 |
|
此时就会进入if (fci_cache.object)
的逻辑了。
我们可以注释掉main_func
的以下代码:
1 | if (fci_cache.object) |
1 | if (fci_cache.object) |
然后重新编译、安装扩展。接着执行这个测试脚本:
1 | [root@592b0366acbf coroutine]# php test.php |
不出意外,会得到这两个错误。我们来跟踪代码的执行流程。
首先,程序会进入main_func
函数里面,并且调用zend_execute_ex(EG(current_execute_data));
来执行我们的协程任务函数。zend_execute_ex
对应的就是PHP
内核的execute_ex
这个函数(在文件zend_vm_execute.h
里面)。接着,执行ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER
这个PHP
的handler
。然后,在这个handler
里面调用fbc->internal_function.handler(call, ret);
方法,而这个handler
实际上就是我们的var_dump
函数了。函数如下:
1 | PHP_FUNCTION(var_dump) |
args
就是我们传递给var_dump
函数的参数,argc
则是我们传递给var_dump
函数的参数个数。如果我们去调试的话,会发现args[0]
的类型是object
,也就是我们的Test
类对象。
然后,调用php_var_dump
来打印变量信息。此时会进入这个case
分支:
1 | case IS_OBJECT: |