是的,我又來了,我帶着個人文章表情包回來。php
再這感謝swoole
大佬們的點贊和轉載,讓我短暫的感覺到了什麼要叫高光時刻。html
我相信大部分人一開始用swoole的協程的時候都會再協程裏寫了一大堆堵塞的函數,致使項目崩潰。(是的!不要告訴我!就我一我的!)
在你們瞭解上一篇【菜鳥光系列】淺談SWOOLE協程篇
能夠了解到協程的建立、yield
、resume
的相關流程和代碼。
因此咱們能夠猜到在協程執行IO堵塞的相關的代碼段是須要主動去yield
而且在reactor
監聽,那麼使用原生的php的函數(例curl、文件操做、sleep....)是不可能會主動觸發yield()
mysql
<?php $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;
輸出內容react
done1 done2 done3 over 6
以上就是一個反面例子,下面列舉下在協程裏那些不能調用的函數redis
* mysql、mysqli、pdo以及其餘DB操做函數 * sleep、usleep * curl_*相關函數 * stream、socket擴展的函數 * swoole\_client同步模式 * memcache、redis擴展函數
來自swoole的官方文檔 https://wiki.swoole.com/wiki/...
那麼確定有人會說,哇 我用個協程還要拿小本本記住下那麼多不用調用的,誰家孩子受得了啊。事實上總有不少人再協程上調用各類IO堵塞的函數sql
因此swoole
那些大佬爲了讓咱們這些孩子可以愉快的使用協程,掉禿嚕皮了想到了一鍵協程化。shell
那咱們來瞅瞅官方說的(一鍵協程化讓我想起了之前的一鍵環境安裝的工具。真的是菜鳥福音,髮際線的恩人!)segmentfault
針對上述問題,咱們換了實現思路,採用 Hook
原生 PHP
函數的方式實現協程客戶端,經過一行代碼就可讓原來的同步 IO 的代碼變成能夠協程調度的異步 IO,即一鍵協程化
。swoole
https://wiki.swoole.com/#/run...
又到了劃重點提問題的時候了,Hook
原生PHP
的函數,你們能夠換個角度思考,若是是我來實現,我可能要挨個把PHP
原生堵塞的函數挨個重寫成支持協程的方式,可是這樣的工做量成本特別的巨大,因此爲了驗證本身的猜測來分析下一鍵協程化的源碼實現app
爲了避免誤導你們 這裏使用的swoole版本爲最新的4.5.2的源碼
一鍵協程化提供給PHP
的API
Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL);
$flags選項 有
對應的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
社區和貢獻者比個心心
文章純屬本身根據代碼和資料理解,若是有錯誤麻煩提出來,倍感萬分,若是由於一些錯誤的觀點被誤導我只能說