• Swoole从入门到入土(28)——协程[核心API]


    本节专门介绍swoole提供的协程机制中核心的API

    类方法:

    1) set():协程设置,设置协程相关选项。

    SwooleCoroutine::set(array $options);

    2) getOptions():获取设置的协程相关选项。

    SwooleCoroutine::getOptions(): null|array;

    3) create():创建一个新的协程,并立即执行。

    SwooleCoroutine::create(callable $function, ...$args): int|false
    
    go(callable $function, ...$args): int|false // 参考php.ini的use_shortname配置

    $function:协程执行的代码,必须为 callable,系统能创建的协程总数量受限于 server->max_coroutine 设置

    返回值:创建失败返回 false,创建成功返回协程的 ID。

    注意:由于底层会优先执行子协程的代码,因此只有子协程挂起时,Coroutine::create 才会返回,继续执行当前协程的代码。在一个协程中使用 go 嵌套创建新的协程。因为 Swoole 的协程是单进程单线程模型,因此,使用 go 创建的子协程会优先执行,子协程执行完毕或挂起时,将重新回到父协程向下执行代码;如果子协程挂起后,父协程退出,不影响子协程的执行。如下示例:

    Co
    un(function() {
        go(function () {
            Co::sleep(3.0);
            go(function () {
                Co::sleep(2.0);
                echo "co[3] end
    ";
            });
            echo "co[2] end
    ";
        });
    
        Co::sleep(1.0);
        echo "co[1] end
    ";
    });

    注意:

    ·每个协程都是相互独立的,需要创建单独的内存空间 (栈内存),在 PHP-7.2 版本中底层会分配 8K 的 stack 来存储协程的变量,zval 的尺寸为 16字节,因此 8K 的 stack 最大可以保存 512 个变量。协程栈内存占用超过 8K 后 ZendVM 会自动扩容。协程退出时会释放申请的 stack 内存。
    ·PHP-7.1、PHP-7.0 默认会分配 256K 栈内存
    ·可调用 Co::set(['stack_size' => 4096]) 修改默认的栈内存尺寸

    4) defer():defer 用于资源的释放,会在协程关闭之前 (即协程函数执行完毕时) 进行调用,就算抛出了异常,已注册的 defer 也会被执行。

    SwooleCoroutine::defer(callable $function);
    
    defer(callable $function); // 短名API

    注意:需要注意的是,它的调用顺序是逆序的(先进后出), 也就是先注册 defer 的后执行,先进后出。逆序符合资源释放的正确逻辑,后申请的资源可能是基于先申请的资源的,如先释放先申请的资源,后申请的资源可能就难以释放。

    示例:

    go(function () {
        defer(function () use ($db) {
            $db->close();
        });
    });

    5) exists():判断指定协程是否存在。

    SwooleCoroutine::exists(int $cid = 0): bool

    示例:

    Co
    un(function () {
        go(function () {
            go(function () {
                Co::sleep(0.001);
                var_dump(Co::exists(Co::getPcid())); // 1: true
            });
            go(function () {
                Co::sleep(0.003);
                var_dump(Co::exists(Co::getPcid())); // 3: false
            });
            Co::sleep(0.002);
            var_dump(Co::exists(Co::getPcid())); // 2: false
        });
    });

     6) getCid():获取当前协程的唯一 ID, 它的别名为 getuid, 是一个进程内唯一的正整数。

    SwooleCoroutine::getCid(): int

    返回值:成功时返回当前协程 ID;如果当前不在协程环境中,则返回 -1

     7) getPcid():获取当前协程的父 ID。 

    SwooleCoroutine::getPcid([$cid]): int

    $cid:协程 cid,参数缺省,可传入某个协程的 id 以获取它的父 id

    示例:

    var_dump(Co::getPcid());
    Co
    un(function () {
        var_dump(Co::getPcid());
        go(function () {
            var_dump(Co::getPcid());
            go(function () {
                var_dump(Co::getPcid());
                go(function () {
                    var_dump(Co::getPcid());
                });
                go(function () {
                    var_dump(Co::getPcid());
                });
                go(function () {
                    var_dump(Co::getPcid());
                });
            });
            var_dump(Co::getPcid());
        });
        var_dump(Co::getPcid());
    });
    var_dump(Co::getPcid());
    
    // bool(false)
    // int(-1)
    // int(1)
    // int(2)
    // int(3)
    // int(3)
    // int(3)
    // int(1)
    // int(-1)
    // bool(false)
    /*
    说明:
    非嵌套协程调用 getPcid 将返回 -1 (从非协程空间创建的)
    在非协程内调用 getPcid 将返回 false (没有父协程)
    0 作为保留 id, 不会出现在返回值中
    */

    注意:协程之间并没有实质上的持续父子关系,协程之间是相互隔离,独立运作的,此 Pcid 可理解为创建了当前协程的协程 id

    8) getContext():获取当前协程的上下文对象。

    SwooleCoroutine::getContext([$cid]): SwooleCoroutineContext

    $cid:协程 cid,可选参数;默认返回当前协程的上下文对象。

    作用:

    · 协程退出后上下文自动清理 (如无其它协程或全局变量引用)
    · 无 defer 注册和调用的开销 (无需注册清理方法,无需调用函数清理)
    · 无 PHP 数组实现的上下文的哈希计算开销 (在协程数量巨大时有一定好处)
    · CoContext 使用 ArrayObject, 满足各种存储需求 (既是对象,也可以以数组方式操作)

    function func(callable $fn, ...$args)
    {
        go(function () use ($fn, $args) {
            $fn(...$args);
            echo 'Coroutine#' . Co::getCid() . ' exit' . PHP_EOL;
        });
    }
    
    /**
    * Compatibility for lower version
    * @param object|Resource $object
    * @return int
    */
    function php_object_id($object)
    {
        static $id = 0;
        static $map = [];
        $hash = spl_object_hash($object);
        return $map[$hash] ?? ($map[$hash] = ++$id);
    }
    
    class Resource
    {
        public function __construct()
        {
            echo __CLASS__ . '#' . php_object_id((object)$this) . ' constructed' . PHP_EOL;
        }
    
        public function __destruct()
        {
            echo __CLASS__ . '#' . php_object_id((object)$this) . ' destructed' . PHP_EOL;
        }
    }
    
    $context = new CoContext();
    assert($context instanceof ArrayObject);
    assert(Co::getContext() === null);
    func(function () {
        $context = Co::getContext();
        assert($context instanceof CoContext);
        $context['resource1'] = new Resource;
        $context->resource2 = new Resource;
        func(function () {
            Co::getContext()['resource3'] = new Resource;
            Co::yield();
            Co::getContext()['resource3']->resource4 = new Resource;
            Co::getContext()->resource5 = new Resource;
        });
    });
    Co::resume(2);
    
    SwooleEvent::wait();
    
    // --EXPECT--
    // Resource#1 constructed
    // Resource#2 constructed
    // Resource#3 constructed
    // Coroutine#1 exit
    // Resource#2 destructed
    // Resource#1 destructed
    // Resource#4 constructed
    // Resource#5 constructed
    // Coroutine#2 exit
    // Resource#5 destructed
    // Resource#3 destructed
    // Resource#4 destructed

    9) yield():手动让出当前协程的执行权。而不是基于 IO 的协程调度;此方法拥有另外一个别名:Coroutine::suspend()

    SwooleCoroutine::yield();

    注意:必须与 Coroutine::resume() 方法成对使用。该协程 yield 以后,必须由其他外部协程 resume,否则将会造成协程泄漏,被挂起的协程永远不会执行。

    示例:

    $cid = go(function () {
        echo "co 1 start
    ";
        co::yield();
        echo "co 1 end
    ";
    });
    
    go(function () use ($cid) {
        echo "co 2 start
    ";
        co::sleep(0.5);
        co::resume($cid);
        echo "co 2 end
    ";
    });
    SwooleEvent::wait();

    10) resume():手动恢复某个协程,使其继续运行,不是基于 IO 的协程调度。

    SwooleCoroutine::resume(int $coroutineId);

    $coroutineId:为要恢复的协程 ID

    注意:当前协程处于挂起状态时,另外的协程中可以使用 resume 再次唤醒当前协程

    示例:

    $id = go(function(){
        $id = co::getuid();
        echo "start coro $id
    ";
        Co::suspend();
        echo "resume coro $id @1
    ";
        Co::suspend();
        echo "resume coro $id @2
    ";
    });
    echo "start to resume $id @1
    ";
    Co::resume($id);
    echo "start to resume $id @2
    ";
    Co::resume($id);
    echo "main
    ";
    SwooleEvent::wait();
    
    // --EXPECT--
    // start coro 1
    // start to resume 1 @1
    // resume coro 1 @1
    // start to resume 1 @2
    // resume coro 1 @2
    // main

    11) list():遍历当前进程内的所有协程。

    SwooleCoroutine::list(): SwooleCoroutineIterator
    
    SwooleCoroutine::listCoroutines(): SwooleCoroitineIterator

    返回值:返回迭代器,可使用 foreach 遍历,或使用 iterator_to_array 转为数组

    示例:

    $coros = SwooleCoroutine::listCoroutines();
    foreach($coros as $cid)
    {
        var_dump(SwooleCoroutine::getBackTrace($cid));
    }

    12) stats():获取协程状态。

    SwooleCoroutine::stats(): array

    返回值:

     示例:

    var_dump(SwooleCoroutine::stats());
    
    array(1) {
      ["c_stack_size"]=>
      int(2097152)
      ["coroutine_num"]=>
      int(132)
      ["coroutine_peak_num"]=>
      int(2)
    }

    13) getBackTrace():获取协程函数调用栈。

    SwooleCoroutine::getBackTrace(int $cid = 0, int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0): array

    $cid:协程的 CID,默认当前协程 CID

    $options:设置选项,默认值:DEBUG_BACKTRACE_PROVIDE_OBJECT 【是否填充 object 的索引】;其它值:DEBUG_BACTRACE_IGNORE_ARGS 【是否忽略 args 的索引,包括所有的 function/method 的参数,能够节省内存开销】

    $limit:限制返回堆栈帧的数量

    返回值:指定的协程不存在,将返回 false;成功返回数组,格式与 debug_backtrace 函数返回值相同。

    示例:

    function test1() {
        test2();
    }
    
    function test2() {
        while(true) {
            co::sleep(10);
            echo __FUNCTION__." 
    ";
        }
    }
    Co
    un(function () {
        $cid = go(function () {
            test1();
        });
    
        go(function () use ($cid) {
            while(true) {
                echo "BackTrace[$cid]:
    -----------------------------------------------
    ";
                //返回数组,需要自行格式化输出
                var_dump(co::getBackTrace($cid))."
    ";
                co::sleep(3);
            }
        });
    });
    SwooleEvent::wait();

    14) printBackTrace():打印协程函数调用栈。参数和 getBackTrace 一致。

    SwooleCoroutine::printBackTrace(int $cid = 0, int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0);

    15) getElapsed():获取协程运行的时间以便于分析统计或找出僵尸协程

    SwooleCoroutine::getElapsed([$cid]): int

    $cid:可选参数,协程的 CID;默认值:当前协程 CID

    返回值:协程已运行的时间浮点数,毫秒级精度

    函数:

    1) batch():并发执行多个协程,并且通过数组,返回这些协程方法的返回值。

    SwooleCoroutineatch(array $tasks, float $timeout = -1): array

    $tasks:传入方法回调的数组,如果指定了 key,则返回值也会被该 key 指向

    $timeout:总的超时时间,超时后会立即返回。但正在运行的协程会继续执行完毕,而不会中止

    返回值:返回一个数组,里面包含回调的返回值。如果 $tasks 参数中,指定了 key,则返回值也会被该 key 指向

    示例:

    use SwooleCoroutine;
    use function SwooleCoroutineatch;
    
    Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL]);
    
    $start_time = microtime(true);
    Coroutine
    un(function () {
        $use = microtime(true);
        $results = batch([
            'file_put_contents' => function () {
                return file_put_contents(__DIR__ . '/greeter.txt', "Hello,Swoole.");
            },
            'gethostbyname' => function () {
                return gethostbyname('localhost');
            },
            'file_get_contents' => function () {
                return file_get_contents(__DIR__ . '/greeter.txt');
            },
            'sleep' => function () {
                sleep(1);
                return true; // 返回NULL 因为超过了设置的超时时间0.1秒,超时后会立即返回。但正在运行的协程会继续执行完毕,而不会中止。
            },
            'usleep' => function () {
                usleep(1000);
                return true;
            },
        ], 0.1);
        $use = microtime(true) - $use;
        echo "Use {$use}s, Result:
    ";
        var_dump($results);
    });
    $end_time =  microtime(true) - $start_time;
    echo "Use {$end_time}s, Done
    ";

    2) parallel():并发执行多个协程。

    SwooleCoroutineparallel(int $n, callable $fn): void

    $n:设置最大的协程数为 $n

    $fn:对应需要执行的回调函数

    示例 :

    use SwooleCoroutine;
    use SwooleCoroutineSystem;
    use function SwooleCoroutineparallel;
    
    $start_time = microtime(true);
    Coroutine
    un(function () {
        $use = microtime(true);
        $results = [];
        parallel(2, function () use (&$results) {
            System::sleep(0.2);
            $results[] = System::gethostbyname('localhost');
        });
        $use = microtime(true) - $use;
        echo "Use {$use}s, Result:
    ";
        var_dump($results);
    });
    $end_time =  microtime(true) - $start_time;
    echo "Use {$end_time}s, Done
    ";

    3) map():类似于 array_map,为数组的每个元素应用回调函数。

    SwooleCoroutinemap(array $list, callable $fn, float $timeout = -1): array

    $list:运行 $fn 函数的数组

    $fn:$list 数组中的每个元素需要执行的回调函数

    $timeout:总的超时时间,超时后会立即返回。但正在运行的协程会继续执行完毕,而不会中止

    示例:

    use SwooleCoroutine;
    use function SwooleCoroutinemap;
    
    function fatorial(int $n): int
    {
        return array_product(range($n, 1));
    }
    
    Coroutine
    un(function () {
        $results = map([2, 3, 4], 'fatorial'); 
        print_r($results);
    });

    4) deadlock_check():协程死锁检测,调用时会输出相关堆栈信息;默认开启,在 EventLoop 终止后,如果存在协程死锁,底层会自动调用;可以通过在 Coroutine::set 中设置 enable_deadlock_check 进行关闭。

    SwooleCoroutinedeadlock_check();

    以上这些就是协程核心API与使用方法了,下一节我们将一起了解swoole为我们带来的系统API

    ---------------------------  我是可爱的分割线  ----------------------------

    最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。

  • 相关阅读:
    无题
    无题
    Windows NT 和 VMS: 其余的故事 (The Rest of the Story)
    Lachesis Shield Released!
    最近几年来看到的最强的照片
    有关 Nintendo GameCube
    那些带给我欢乐的游戏
    习惯了 .#
    Rootkits
    我写的IDA插件发布了
  • 原文地址:https://www.cnblogs.com/ddcoder/p/14286785.html
Copyright © 2020-2023  润新知