上一篇文章,我们分析了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: |