【菜鳥光系列】淺淡SWOOLE協程(二) 一鍵協程化

前言

是的,我又來了,我帶着個人文章表情包回來。php

再這感謝swoole大佬們的點贊和轉載,讓我短暫的感覺到了什麼要叫高光時刻。
html

背景

我相信大部分人一開始用swoole的協程的時候都會再協程裏寫了一大堆堵塞的函數,致使項目崩潰。(是的!不要告訴我!就我一我的!)
image.png
在你們瞭解上一篇【菜鳥光系列】淺談SWOOLE協程篇
能夠了解到協程的建立、yieldresume的相關流程和代碼。
因此咱們能夠猜到在協程執行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

那些傳說中的php堵塞函數

*   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的源碼

一鍵協程化提供給PHPAPI

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

$flags選項 有

  1. SWOOLE_HOOK_ALL(不包括 CURL 不要被ALL給迷惑了)
  2. SWOOLE_HOOK_TCP
  3. SWOOLE_HOOK_UNIX
  4. SWOOLE_HOOK_UDP
  5. SWOOLE_HOOK_UDG
  6. SWOOLE_HOOK_SSL
  7. SWOOLE_HOOK_TLS
  8. SWOOLE_HOOK_SLEEP
  9. SWOOLE_HOOK_FILE
  10. SWOOLE_HOOK_STREAM_FUNCTION
  11. SWOOLE_HOOK_BLOCKING_FUNCTION
  12. SWOOLE_HOOK_PROC
  13. 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社區和貢獻者比個心心

最後仍是那句話

文章純屬本身根據代碼和資料理解,若是有錯誤麻煩提出來,倍感萬分,若是由於一些錯誤的觀點被誤導我只能說

相關文章
相關標籤/搜索