当前位置: 首页 > 文档资料 > Swoole 中文文档 >

协程API

优质
小牛编辑
134浏览
2023-12-01

建议先看概览,了解协程基本概念再看此节。

方法

set()

协程设置,设置协程相关选项。

Swoole\Coroutine::set(array $options);
参数此版本后稳定作用
max_coroutine-设置全局最大协程数,超过限制后底层将无法创建新的协程,server下会被server->max_coroutine覆盖。
stack_size-设置单个协程初始栈的内存尺寸,默认为2M
log_levelv4.0.0日志等级 详见
trace_flagsv4.0.0跟踪标签 详见
socket_connect_timeoutv4.2.10建立连接超时时间,参考客户端超时规则
socket_timeoutv4.2.10发送/接收超时,参考客户端超时规则
socket_read_timeoutv4.3.0读超时,参考客户端超时规则
socket_write_timeoutv4.3.0写超时,参考客户端超时规则
dns_cache_expirev4.2.11设置swoole dns缓存失效时间,单位秒,默认60秒
dns_cache_capacityv4.2.11设置swoole dns缓存容量,默认1000
hook_flagsv4.4.0一键协程化的hook范围配置,参考一键协程化
enable_preemptive_schedulerv4.4.0设置打开写成抢占式调度,协程最大执行时间为10ms,会覆盖ini配置
dns_serverv4.5.0设置dns查询的server,默认"8.8.8.8"

create()

创建一个新的协程,并立即执行。

Swoole\Coroutine::create(callable $function, ...$args) : int|false;
go(callable $function, ...$args) : int|false; // 参考php.ini的use_shortname配置
  • 参数

    • callable $function
      • 功能:协程执行的代码,必须为callable,系统能创建的协程总数量受限于server->max_coroutine设置
      • 默认值:无
      • 其它值:无
  • 返回值

    • 创建失败返回false
    • 创建成功返回协程的ID

!> 由于底层会优先执行子协程的代码,因此只有子协程挂起时,Coroutine::create才会返回,继续执行当前协程的代码。

  • 执行顺序

    在一个协程中使用go嵌套创建新的协程。因为Swoole的协程是单进程单线程模型,因此:

    • 使用go创建的子协程会优先执行,子协程执行完毕或挂起时,将重新回到父协程向下执行代码
    • 如果子协程挂起后,父协程退出,不影响子协程的执行
    go(function() {
      go(function () {
          co::sleep(3.0);
          go(function () {
              co::sleep(2.0);
              echo "co[3] end\n";
          });
          echo "co[2] end\n";
      });
    
      co::sleep(1.0);
      echo "co[1] end\n";
    });
  • 协程开销

    每个协程都是相互独立的,需要创建单独的内存空间(栈内存),在PHP-7.2版本中底层会分配8Kstack来存储协程的变量,zval的尺寸为16字节,因此8Kstack最大可以保存512个变量。协程栈内存占用超过8KZendVM会自动扩容。

    协程退出时会释放申请的stack内存。

    • PHP-7.1PHP-7.0默认会分配256K栈内存
    • 可调用Co::set(['stack_size' => 4096])修改默认的栈内存尺寸

defer()

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

!> Swoole版本 >= 4.2.9

Swoole\Coroutine::defer(callable $function);
defer(callable $function); // 短名API

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

  • 示例

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

exists()

判断指定协程是否存在。

Swoole\Coroutine::exists(int $cid = 0): bool

!> Swoole版本 >= v4.3.0

  • 示例

    go(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
      });
    });

getCid()

获取当前协程的唯一ID, 它的别名为getUid, 是一个进程内唯一的正整数。

Swoole\Coroutine::getCid(): int
  • 返回值

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

getPcid()

获取当前协程的父ID

Swoole\Coroutine::getPcid([$cid]): int

!> Swoole版本 >= v4.3.0

  • 参数

    • int $cid
      • 功能:协程 cid,参数缺省, 可传入某个协程的id以获取它的父id
      • 默认值:当前协程
      • 其它值:无
  • 示例

    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());
                });
                go(function () {
                    var_dump(Co::getPcid());
                });
            });
            var_dump(Co::getPcid());
        });
        var_dump(Co::getPcid());
    });
    var_dump(Co::getPcid());
    
    // --EXPECT--
    
    // 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

  • 用途

    • 串联多个协程调用栈

      go(function () {
          go(function () {
              $ptrace = Co::getBackTrace(Co::getPcid());
              // balababala
              var_dump(array_merge($ptrace, Co::getBackTrace(Co::getCid())));
          });
      });

getContext()

获取当前协程的上下文对象。

Swoole\Coroutine::getContext([$cid]): Swoole\Coroutine\Context

!> Swoole版本 >= v4.3.0

  • 参数

    • int $cid
      • 功能:协程 cid,可选参数
      • 默认值:返回当前协程的上下文对象
      • 其它值:无
  • 作用

    • 协程退出后上下文自动清理 (如无其它协程或全局变量引用)
    • defer注册和调用的开销 (无需注册清理方法, 无需调用函数清理)
    • 无PHP数组实现的上下文的哈希计算开销 (在协程数量巨大时有一定好处)
    • Co\Context使用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 Co\Context();
    assert($context instanceof ArrayObject);
    assert(Co::getContext() === null);
    func(function () {
        $context = Co::getContext();
        assert($context instanceof Co\Context);
        $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);
    
    // --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

yield()

手动让出当前协程的执行权。而不是基于IO的协程调度

此方法拥有另外一个别名:Coroutine::suspend()

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

Swoole\Coroutine::yield();
  • 示例

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

resume()

手动恢复某个协程,使其继续运行,不是基于IO的协程调度

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

Swoole\Coroutine::resume(int $coroutineId);
  • 参数

    • int $coroutineId
      • 功能:为要恢复的协程ID
      • 默认值:无
      • 其它值:无
  • 示例

    use Swoole\Coroutine as co;
    $id = go(function(){
        $id = co::getUid();
        echo "start coro $id\n";
        co::suspend();
        echo "resume coro $id @1\n";
        co::suspend();
        echo "resume coro $id @2\n";
    });
    echo "start to resume $id @1\n";
    co::resume($id);
    echo "start to resume $id @2\n";
    co::resume($id);
    echo "main\n";
    
    // --EXPECT--
    // start coro 1
    // start to resume 1 @1
    // resume coro 1 @1
    // start to resume 1 @2
    // resume coro 1 @2
    // main

list()

遍历当前进程内的所有协程。

Swoole\Coroutine::list(): Coroutine\Iterator
Swoole\Coroutine::listCoroutines(): Coroitine\Iterator

!> v4.3.0以下版本需使用listCoroutines, 新版本缩略了该方法的名称并将listCoroutines设为别名。listv4.1.0或更高版本可用。

  • 返回值

    • 返回迭代器,可使用foreach遍历,或使用iterator_to_array转为数组
    $coros = Swoole\Coroutine::listCoroutines();
    foreach($coros as $cid)
    {
        var_dump(Swoole\Coroutine::getBackTrace($cid));
    }

stats()

获取协程状态。

Swoole\Coroutine::stats(): array
  • 返回值
key作用
event_num当前reactor事件数量
signal_listener_num当前监听信号的数量
aio_task_num异步IO任务数量 (这里的aio指文件IO或dns, 不包含其它网络IO, 下同)
aio_worker_num异步IO工作线程数量
c_stack_size每个协程的C栈大小
coroutine_num当前运行的协程数量
coroutine_peak_num当前运行的协程数量的峰值
coroutine_last_cid最后创建协程的id
  • 示例

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

getBackTrace()

获取协程函数调用栈。

Swoole\Coroutine::getBackTrace(int $cid=0, int $options=DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit=0): array;

!> Swoole版本 >= v4.1.0

  • 参数

    • int $cid

      • 功能:协程的CID
      • 默认值:当前协程 CID
      • 其它值:无
    • int $options

      • 功能:设置选项
      • 默认值DEBUG_BACKTRACE_PROVIDE_OBJECT 【是否填充object的索引】
      • 其它值DEBUG_BACKTRACE_IGNORE_ARGS 【是否忽略args的索引,包括所有的 function/method 的参数,能够节省内存开销】
    • *`int limit`**

      • 功能:限制返回堆栈帧的数量
      • 默认值0
      • 其它值:无
  • 返回值

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

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