swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化

前言

是的,我又来了,我带着我的文章表情包回来。

再这感谢swoole大佬们的点赞和转载,让我短暂的感受到了什么要叫高光时刻。

swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化_第1张图片

背景

我相信大部分人一开始用swoole的协程的时候都会再协程里写了一大堆堵塞的函数,导致项目崩溃。(是的!不要告诉我!就我一个人!)

swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化_第2张图片

在大家了解上一篇【菜鸟光系列】浅谈SWOOLE协程篇

可以了解到协程的创建、yield、resume的相关流程和代码。

所以我们可以猜到在协程执行IO堵塞的相关的代码段是需要主动去yield并且在reactor监听,那么使用原生的php的函数(例curl、文件操作、sleep....)是不可能会主动触发yield()

$time = time();

go(function () {

sleep(2);

echo "done1" . PHP_EOL;

});

go(function () {

sleep(2);

echo "done2" . PHP_EOL;

});

go(function () {

sleep(2);

echo "done3" . PHP_EOL;

});

echo "over" . PHP_EOL;

echo time() - $time;

输出内容

done1

done2

done3

over

6

以上就是一个反面例子,下面列举下在协程里那些不能调用的函数

swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化_第3张图片

那些传说中的php堵塞函数

* mysql、mysqli、pdo以及其他DB操作函数

* sleep、usleep

* curl_*相关函数

* stream、socket扩展的函数

* swoole\_client同步模式

* memcache、redis扩展函数

那么肯定有人会说,哇 我用个协程还要拿小本本记住下那么多不用调用的,谁家孩子受得了啊。事实上总有很多人再协程上调用各种IO堵塞的函数

所以swoole那些大佬为了让我们这些孩子能够愉快的使用协程,掉秃噜皮了想到了一键协程化。

一键协程化

那我们来瞅瞅官方说的(一键协程化让我想起了以前的一键环境安装的工具。真的是菜鸟福音,发际线的恩人!)

针对上述问题,我们换了实现思路,采用 Hook 原生 PHP 函数的方式实现协程客户端,通过一行代码就可以让原来的同步 IO 的代码变成可以协程调度的异步 IO,即一键协程化。

又到了划重点提问题的时候了,Hook原生PHP的函数,大家可以换个角度思考,如果是我来实现,我可能要挨个把PHP原生堵塞的函数挨个重写成支持协程的方式,但是这样的工作量成本特别的巨大,所以为了验证自己的猜想来分析下一键协程化的源码实现

swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化_第4张图片

源码分析

为了不误导大家 这里使用的swoole版本为最新的4.5.2的源码

一键协程化提供给PHP的API

Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL);

$flags选项 有

SWOOLE_HOOK_ALL(不包括 CURL 不要被ALL给迷惑了)

SWOOLE_HOOK_TCP

SWOOLE_HOOK_UNIX

SWOOLE_HOOK_UDP

SWOOLE_HOOK_UDG

SWOOLE_HOOK_SSL

SWOOLE_HOOK_TLS

SWOOLE_HOOK_SLEEP

SWOOLE_HOOK_FILE

SWOOLE_HOOK_STREAM_FUNCTION

SWOOLE_HOOK_BLOCKING_FUNCTION

SWOOLE_HOOK_PROC

SWOOLE_HOOK_CURL

对应的flags可以通过我们的塑料英语就能知道基本的对应的意思

我们根据上篇文章提到的函数申明规范,可以在swoole_runtime.cc找到对应的代码

static PHP_METHOD(swoole_runtime, enableCoroutine)

{

zval *zflags = nullptr;

/*TODO:[v4.6] enable SW_HOOK_CURL by default after curl handler completed */

zend_long flags = SW_HOOK_ALL;

ZEND_PARSE_PARAMETERS_START(0, 2)

Z_PARAM_OPTIONAL

Z_PARAM_ZVAL(zflags) // or zenable

Z_PARAM_LONG(flags)

ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);

if (zflags)

{

if (Z_TYPE_P(zflags) == IS_LONG)

{

flags = SW_MAX(0, Z_LVAL_P(zflags));

}

else if (ZVAL_IS_BOOL(zflags))

{

if (!Z_BVAL_P(zflags))

{

flags = 0;

}

}

else

{

const char *space, *class_name = get_active_class_name(&space);

zend_type_error("%s%s%s() expects parameter %d to be %s, %s given", class_name, space, get_active_function_name(), 1, "bool or long", zend_zval_type_name(zflags));

}

}

RETURN_BOOL(PHPCoroutine::enable_hook(flags));

}

根据PHPCoroutine::enable_hook继续往下追

发现了我们的答案

bool PHPCoroutine::enable_hook(int flags)

{

if (!hook_init)

{

HashTable *xport_hash = php_stream_xport_get_hash();

// php_stream

ori_factory.tcp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tcp"));

ori_factory.udp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udp"));

ori_factory._unix = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("unix"));

ori_factory.udg = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udg"));

ori_factory.ssl = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("ssl"));

ori_factory.tls = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tls"));

// file

memcpy((void*) &ori_php_plain_files_wrapper, &php_plain_files_wrapper, sizeof(php_plain_files_wrapper));

function_table = (zend_array*) emalloc(sizeof(zend_array));

zend_hash_init(function_table, 8, NULL, NULL, 0);

hook_init = true;

}

// php_stream

if (flags & SW_HOOK_TCP)

{

if (!(hook_flags & SW_HOOK_TCP))

{

if (php_stream_xport_register("tcp", socket_create) != SUCCESS)

{

flags ^= SW_HOOK_TCP;

}

}

}

else

{

if (hook_flags & SW_HOOK_TCP)

{

php_stream_xport_register("tcp", ori_factory.tcp);

}

}

if (flags & SW_HOOK_UDP)

{

if (!(hook_flags & SW_HOOK_UDP))

{

if (php_stream_xport_register("udp", socket_create) != SUCCESS)

{

flags ^= SW_HOOK_UDP;

}

}

}

else

{

if (hook_flags & SW_HOOK_UDP)

{

php_stream_xport_register("udp", ori_factory.udp);

}

}

if (flags & SW_HOOK_UNIX)

{

if (!(hook_flags & SW_HOOK_UNIX))

{

if (php_stream_xport_register("unix", socket_create) != SUCCESS)

{

flags ^= SW_HOOK_UNIX;

}

}

}

else

{

if (hook_flags & SW_HOOK_UNIX)

{

php_stream_xport_register("unix", ori_factory._unix);

}

}

if (flags & SW_HOOK_UDG)

{

if (!(hook_flags & SW_HOOK_UDG))

{

if (php_stream_xport_register("udg", socket_create) != SUCCESS)

{

flags ^= SW_HOOK_UDG;

}

}

}

else

{

if (hook_flags & SW_HOOK_UDG)

{

php_stream_xport_register("udg", ori_factory.udg);

}

}

if (flags & SW_HOOK_SSL)

{

if (!(hook_flags & SW_HOOK_SSL))

{

if (php_stream_xport_register("ssl", socket_create) != SUCCESS)

{

flags ^= SW_HOOK_SSL;

}

}

}

else

{

if (hook_flags & SW_HOOK_SSL)

{

if (ori_factory.ssl != nullptr) {

php_stream_xport_register("ssl", ori_factory.ssl);

} else {

php_stream_xport_unregister("ssl");

}

}

}

if (flags & SW_HOOK_TLS)

{

if (!(hook_flags & SW_HOOK_TLS))

{

if (php_stream_xport_register("tls", socket_create) != SUCCESS)

{

flags ^= SW_HOOK_TLS;

}

}

}

else

{

if (hook_flags & SW_HOOK_TLS)

{

if (ori_factory.tls != nullptr)

{

php_stream_xport_register("tls", ori_factory.tls);

}

else

{

php_stream_xport_unregister("tls");

}

}

}

if (flags & SW_HOOK_STREAM_FUNCTION)

{

if (!(hook_flags & SW_HOOK_STREAM_FUNCTION))

{

SW_HOOK_FUNC(stream_select);

SW_HOOK_FUNC(stream_socket_pair);

}

}

else

{

if (hook_flags & SW_HOOK_STREAM_FUNCTION)

{

SW_UNHOOK_FUNC(stream_select);

SW_UNHOOK_FUNC(stream_socket_pair);

}

}

// file

if (flags & SW_HOOK_FILE)

{

if (!(hook_flags & SW_HOOK_FILE))

{

memcpy((void*) &php_plain_files_wrapper, &sw_php_plain_files_wrapper, sizeof(php_plain_files_wrapper));

}

}

else

{

if (hook_flags & SW_HOOK_FILE)

{

memcpy((void*) &php_plain_files_wrapper, &ori_php_plain_files_wrapper, sizeof(php_plain_files_wrapper));

}

}

// sleep

if (flags & SW_HOOK_SLEEP)

{

if (!(hook_flags & SW_HOOK_SLEEP))

{

SW_HOOK_FUNC(sleep);

SW_HOOK_FUNC(usleep);

SW_HOOK_FUNC(time_nanosleep);

SW_HOOK_FUNC(time_sleep_until);

}

}

else

{

if (hook_flags & SW_HOOK_SLEEP)

{

SW_UNHOOK_FUNC(sleep);

SW_UNHOOK_FUNC(usleep);

SW_UNHOOK_FUNC(time_nanosleep);

SW_UNHOOK_FUNC(time_sleep_until);

}

}

// proc_open

if (flags & SW_HOOK_PROC)

{

if (!(hook_flags & SW_HOOK_PROC))

{

SW_HOOK_FUNC(proc_open);

SW_HOOK_FUNC(proc_close);

SW_HOOK_FUNC(proc_get_status);

SW_HOOK_FUNC(proc_terminate);

}

}

else

{

if (hook_flags & SW_HOOK_PROC)

{

SW_UNHOOK_FUNC(proc_open);

SW_UNHOOK_FUNC(proc_close);

SW_UNHOOK_FUNC(proc_get_status);

SW_UNHOOK_FUNC(proc_terminate);

}

}

// blocking function

if (flags & SW_HOOK_BLOCKING_FUNCTION)

{

if (!(hook_flags & SW_HOOK_BLOCKING_FUNCTION))

{

hook_func(ZEND_STRL("gethostbyname"), PHP_FN(swoole_coroutine_gethostbyname));

hook_func(ZEND_STRL("exec"));

hook_func(ZEND_STRL("shell_exec"));

}

}

else

{

if (hook_flags & SW_HOOK_BLOCKING_FUNCTION)

{

SW_UNHOOK_FUNC(gethostbyname);

SW_UNHOOK_FUNC(exec);

SW_UNHOOK_FUNC(shell_exec);

}

}

if (flags & SW_HOOK_CURL)

{

if (!(hook_flags & SW_HOOK_CURL))

{

hook_func(ZEND_STRL("curl_init"));

hook_func(ZEND_STRL("curl_setopt"));

hook_func(ZEND_STRL("curl_setopt_array"));

hook_func(ZEND_STRL("curl_exec"));

hook_func(ZEND_STRL("curl_getinfo"));

hook_func(ZEND_STRL("curl_errno"));

hook_func(ZEND_STRL("curl_error"));

hook_func(ZEND_STRL("curl_reset"));

hook_func(ZEND_STRL("curl_close"));

hook_func(ZEND_STRL("curl_multi_getcontent"));

}

}

else

{

if (hook_flags & SW_HOOK_CURL)

{

SW_UNHOOK_FUNC(curl_init);

SW_UNHOOK_FUNC(curl_setopt);

SW_UNHOOK_FUNC(curl_setopt_array);

SW_UNHOOK_FUNC(curl_exec);

SW_UNHOOK_FUNC(curl_getinfo);

SW_UNHOOK_FUNC(curl_errno);

SW_UNHOOK_FUNC(curl_error);

SW_UNHOOK_FUNC(curl_reset);

SW_UNHOOK_FUNC(curl_close);

SW_UNHOOK_FUNC(curl_multi_getcontent);

}

}

hook_flags = flags;

return true;

}

不要函数这么长给吓到了,这段函数非常简单明了甚至不用加中文备注,根据传入不同的FLAG进行hook_func对应的模块的函数

我们可以看到关键核心的代码为SW_UNHOOK_FUNC,hook_func

拿我们上一篇聊的sleep的例子来说

SW_HOOK_FUNC(sleep);

#define SW_HOOK_FUNC(f) hook_func(ZEND_STRL(#f), PHP_FN(swoole_##f))

那么替换sleep的函数为swoole_sleep

static PHP_FUNCTION(swoole_sleep)

{

....

RETURN_LONG(System::sleep((double ) num) < 0 ?

....

}

呀!这不就我们熟悉的System::sleep了吗

分析到这里与开头的猜想一致,如果方便大家在协程里面放飞的编码和无缝的项目切入swoole,需要巨大的工程对php原生的函数的替换,真的是非常不容易!

写给最后

立个flag今年写完swoole系列,向swoole社区和贡献者比个心心

3dcfe2273117fced7d1d0f704c779842.gif

最后还是那句话

文章纯属自己根据代码和资料理解,如果有错误麻烦提出来,倍感万分,如果因为一些错误的观点被误导我只能说

swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化_第5张图片

你可能感兴趣的:(swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化)