协程API
建议先看概览,了解协程基本概念再看此节。
方法
set()
协程设置,设置协程相关选项。
Swoole\Coroutine::set(array $options);
参数 | 此版本后稳定 | 作用 |
---|---|---|
max_coroutine | - | 设置全局最大协程数,超过限制后底层将无法创建新的协程,server下会被server->max_coroutine覆盖。 |
stack_size | - | 设置单个协程初始栈的内存尺寸,默认为2M |
log_level | v4.0.0 | 日志等级 详见 |
trace_flags | v4.0.0 | 跟踪标签 详见 |
socket_connect_timeout | v4.2.10 | 建立连接超时时间,参考客户端超时规则 |
socket_timeout | v4.2.10 | 发送/接收超时,参考客户端超时规则 |
socket_read_timeout | v4.3.0 | 读超时,参考客户端超时规则 |
socket_write_timeout | v4.3.0 | 写超时,参考客户端超时规则 |
dns_cache_expire | v4.2.11 | 设置swoole dns缓存失效时间,单位秒,默认60秒 |
dns_cache_capacity | v4.2.11 | 设置swoole dns缓存容量,默认1000 |
hook_flags | v4.4.0 | 一键协程化的hook范围配置,参考一键协程化 |
enable_preemptive_scheduler | v4.4.0 | 设置打开写成抢占式调度,协程最大执行时间为10ms,会覆盖ini配置 |
dns_server | v4.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
版本中底层会分配8K
的stack
来存储协程的变量,zval
的尺寸为16字节
,因此8K
的stack
最大可以保存512
个变量。协程栈内存占用超过8K
后ZendVM
会自动扩容。协程退出时会释放申请的
stack
内存。PHP-7.1
、PHP-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
- 默认值:当前协程
- 其它值:无
- 功能:协程 cid,参数缺省, 可传入某个协程的
示例
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
设为别名。list
在v4.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); } });