三十一、Swoole 基础学习笔记 - Swoole 协程核心 API
hi,我是温新,一名PHPer
文章基于 Swoole 5.0.1 版本编写。
学习目标:了解协程相关 API
说明:本篇文章结合官方文档编写及参考网络资料编写,虽非全部原创,但也是结合了自己的理解,若转载请附带本文 URL,编写不易,持续编写更不易,谢谢!
本篇文章学习协程的相关 API。
create
语法:
Swoole\Coroutine::create(callable $function, ...$args): int|false
go(callable $function, ...$args): int|false // 参考php.ini的use_shortname配置
含义:创建一个新的协程,并立即执行。
返回值:成功返回协程 ID;失败返回 false。
$id1 = Swoole\Coroutine::create(function () {
Swoole\Coroutine::sleep(1);
echo 'hi,我是王美丽,我来自协程'. PHP_EOL;
});
echo '协程 ID ' . $id1 . PHP_EOL;
// 短名称创建协程
$id2 = go(function () {
echo 'hi, 我是郝帅,我也来自协程,我是来寻找王美丽' . PHP_EOL;
});
echo '协程 ID ' . $id2 . PHP_EOL;
协程开销:
每个协程都是相互独立的,需要创建单独的内存空间 (栈内存),在 PHP-7.2
版本中底层会分配 8K
的 stack
来存储协程的变量,zval
的尺寸为 16字节
,因此 8K
的 stack
最大可以保存 512
个变量。协程栈内存占用超过 8K
后 ZendVM
会自动扩容。
set
含义:协程设置,设置协程相关选项。关于参数参考文档。
语法:
Swoole\Coroutine::set(array $options);
getOptions
含义:获取设置的协程相关选项。
语法:
Swoole\Coroutine::getOptions(): null|array;
// 设置参数
Swoole\Coroutine::set([
// 全局最大线程数
'max_coroutine' => 10,
// 最大并发请求数
'max_concurrency' => 20,
]);
Swoole\Coroutine::create(function () {
// 设置参数
print_r(Swoole\Coroutine::getOptions());
});
exists
含义:判断指定协程是否存在。
语法:
Swoole\Coroutine::exists(int $cid = 0): bool
案例
$coroutineId = Swoole\Coroutine::create(function () {
echo 'hi,来自外星的王美丽,欢迎你,这是地球' . PHP_EOL;
if (Swoole\Coroutine::exists(Co::getCid())) {
echo 'hi,王美丽,我是郝帅' . PHP_EOL;
}
});
getCid
含义:获取当前协程的唯一 ID
, 它的别名为 getuid
, 是一个进程内唯一的正整数。
语法:
Swoole\Coroutine::getCid(): int
返回值:成功返回当前协程 ID,失败返回 -1。
getPcid
含义:获取当前协程的父 ID
。
语法:
Swoole\Coroutine::getPcid([$cid]): int
返回值:协程 cid,参数缺省,可传入某个协程的 id
以获取它的父 id
。
echo Swoole\Coroutine::getPcid() . PHP_EOL;
// 协程容器
\Co\run(function () {
// 不在协程环境中
echo Swoole\Coroutine::getPcid() . PHP_EOL;
go(function () {
// 返回值 1,在协程环境中
echo Swoole\Coroutine::getPcid() . PHP_EOL;
// 返回值 2,当前协程 ID
$cid = Swoole\Coroutine::getCid();
echo '当前协程 ID' . $cid . PHP_EOL;
echo '当前协程父ID' . Swoole\Coroutine::getPcid($cid) . PHP_EOL;
});
});
yield
含义:手动让出当前协程的执行权。
语法:Swoole\Coroutine::yield();
案例:
$cid = Swoole\Coroutine::create(function () {
echo 'co 1 start' . PHP_EOL;
// 让出当前协程执行权
Swoole\Coroutine::yield();
echo 'co 1 end' . PHP_EOL;
});
Swoole\Coroutine::create(function () use ($cid) {
echo 'co 2 start' . PHP_EOL;
Swoole\Coroutine::sleep(1);
// 恢复让出执行权的协程执行
Swoole\Coroutine::resume($cid);
echo 'co 2 end' . PHP_EOL;
});
输出结果
$php 31-swoole-coroutine-api.php
co 1 start
co 2 start
co 1 end
co 2 end
释义:
1、代码执行第一个协程,开始输出 co 1 start
;
2、第一个协程遇到 yield
,退出该协程执行,保存当前环境;
3、第二个协程执行,输出 co 2 start
;然后睡一秒;
4、第二个协程睡一后开始执行,遇到手动恢复要执行的第一个开始,返回到第一个协程,输出 co 1 end
;
5、第一个协程执行完毕后,返回到第二个协程,然后输出 co 2 end
。
resume
含义:手动恢复某个协程,使其继续运行,不是基于 IO 的协程调度。
语法:Swoole\Coroutine::resume(int $coroutineId);
案例:
$id = Swoole\Coroutine::create(function () {
// 不知道是什么 ID,文档说明
$id = Co::getuid();
echo 'start co ' . $id . PHP_EOL;
Swoole\Coroutine::yield();
echo 'resume co ' . $id . ' @1' . PHP_EOL;
Swoole\Coroutine::yield();
echo 'resume co ' . $id . ' @2' . PHP_EOL;
});
echo 'start to resume ' . $id . '@1' . PHP_EOL;
Co::resume($id);
echo 'start to resume ' . $id . '@2' . PHP_EOL;
Co::resume($id);
echo 'end' . PHP_EOL;
输出结果
$php 31-swoole-coroutine-api.php
start co 1
start to resume 1@1
resume co 1 @1
start to resume 1@2
resume co 1 @2
end
list
含义:遍历当前进程内的所有协程。
语法:
Swoole\Coroutine::list(): Swoole\Coroutine\Iterator
Swoole\Coroutine::listCoroutines(): Swoole\Coroitine\Iterator
案例:
Swoole\Coroutine::create(function () {
Swoole\Coroutine::create(function () {
$coros = Swoole\Coroutine::list();
foreach ($coros as $cid) {
print_r(Swoole\Coroutine::getBackTrace($cid));
}
});
});
输出结果:
Array
(
[0] => Array
(
[file] => /home/www/test/31-swoole-coroutine-api.php
[line] => 113
[function] => getBackTrace
[class] => Swoole\Coroutine
[type] => ::
[args] => Array
(
[0] => 2
)
)
[1] => Array
(
[function] => {closure}
[args] => Array()
)
)
getBackTrace
含义:获取协程函数调用栈。
语法:
Swoole\Coroutine::getBackTrace(int $cid = 0, int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0): array
参数:
$cid:协程的 CID;
$options:设置选项
默认值:
DEBUG_BACKTRACE_PROVIDE_OBJECT
【是否填充object
的索引】 其它值:
DEBUG_BACKTRACE_IGNORE_ARGS
【是否忽略 args 的索引,包括所有的 function/method 的参数,能够节省内存开销】$limit:限制返回堆栈帧的数量
案例:
function test1() {
test2();
}
function test2() {
while (true) {
Swoole\Coroutine::sleep(5);
echo __FUNCTION__ . PHP_EOL;
}
}
\Co\run(function () {
$cid = Swoole\Coroutine::create(function () {
test1();
});
Swoole\Coroutine::create(function () use ($cid) {
echo 'backTrace ' . $cid . PHP_EOL;
print_r(Swoole\Coroutine::getBackTrace($cid));
Swoole\Coroutine::sleep(3);
echo '当前协程运行时间:' . Swoole\Coroutine::getElapsed($cid). PHP_EOL;
echo '当前协程占用内存:' . Swoole\Coroutine::getStackUsage($cid). PHP_EOL;
});
});
getElapsed
含义:获取协程运行的时间以便于分析统计或找出僵尸协程。
语法:Swoole\Coroutine::getElapsed([$cid]): int
。
返回值:协程已运行的时间浮点数,毫秒级精度。如:协程运行时间:3004
getStackUsage
含义:获取当前 PHP 栈的内存使用量。
语法:Swoole\Coroutine::getStackUsage([$cid]): int
join
含义:并发执行多个协程。
语法:Swoole\Coroutine::join(array $cid_array, float $timeout = -1): bool
参数:
- array $cid_array:需要执行协程的
CID
数组; - float $timetout:总的超时时间,超时后会立即返回。但正在运行的协程会继续。
案例:
Swoole\Coroutine\run(function () {
$status = Swoole\Coroutine::join([
Swoole\Coroutine\go(function () use (&$result) {
$result['baidu'] = strlen(file_get_contents('https://www.baidu.com/'));
}),
Swoole\Coroutine\go(function () use (&$result) {
$result['taobao'] = strlen(file_get_contents('https://www.taobao.com/'));
}),
], 1);
var_dump($result, $status,swoole_strerror(swoole_last_error(), 9));
});
输出结果:
$php 31-swoole-coroutine-api.php
array(2) {
["baidu"]=>
int(9508)
["taobao"]=>
int(87554)
}
bool(true)
string(7) "Success"
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 |
案例:
Swoole\Coroutine::create(function () {
Swoole\COroutine::create(function () {
print_r(Swoole\Coroutine::stats());
});
});
输出结果:
$php 31-swoole-coroutine-api.php
Array
(
[event_num] => 0
[signal_listener_num] => 0
[aio_task_num] => 0
[aio_worker_num] => 0
[aio_queue_size] => 0
[c_stack_size] => 2097152
[coroutine_num] => 2
[coroutine_peak_num] => 2
[coroutine_last_cid] => 2
)
defer
含义:用于资源的释放,会在协程关闭之前 (即协程函数执行完毕时) 进行调用,就算抛出了异常,已注册的 defer
也会被执行。
语法:
Swoole\Coroutine::defer(callable $function);
defer(callable $function); // 短名API
注意:
它的调用顺序是逆序的(先进后出), 也就是先注册
defer
的后执行,先进后出。逆序符合资源释放的正确逻辑,后申请的资源可能是基于先申请的资源的,如先释放先申请的资源,后申请的资源可能就难以释放。
函数 batch
含义:并发执行多个协程,并且通过数组返回这这些方法的返回值。
语法:
Swoole\Coroutine\batch(array $tasks, float $timeout = -1): array
# 参数
array $tasks :传入方法回调的数组,如果指定了 key,则返回值也会被该 key 指向;
float $timeout : 总的超时时间,超时后会立即返回。但正在运行的协程会继续执行完毕,而不会中止
返回值:返回一个数组,里面包含回调的返回值。如果 $tasks
参数中,指定了 key
,则返回值也会被该 key
指向。
案例:
$startTIme = microtime(true);
Swoole\Coroutine::set([
'hook_flags' => SWOOLE_HOOK_ALL
]);
Swoole\Coroutine\run(function () {
$use = microtime(true);
$results = Swoole\Coroutine\batch([
'file_put_contents' => function () {
$content = file_get_contents('https://www.baidu.com');
return file_put_contents(__DIR__ . '/greeter.txt', $content);
},
'gethostbyname' => function () {
return gethostbyname('localhost');
},
'sleep' => function () {
// 返回NULL 因为超过了设置的超时时间0.1秒,
// 超时后会立即返回。但正在运行的协程会继续执行完毕,而不会中止。
sleep(1);
return true;
},
'usleep' => function () {
usleep(1000);
return true;
},
], 0.1);
$use = microtime(true) - $use;
echo '并发执行完成时间:' . $use . '秒' . PHP_EOL;
print_r($results);
});
$endTime = microtime(true) - $startTIme;
echo '花费时间:' . $endTime . '秒' . PHP_EOL;
输出结果:
$php 31-swoole-coroutine-api.php
并发执行完成时间:0.1020028591156秒
Array
(
[file_put_contents] =>
[gethostbyname] => 127.0.0.1
[sleep] =>
[usleep] => 1
)
花费时间:1.0037970542908秒
函数 parallel
含义:并发执行多个协程。
语法:
Swoole\Coroutine\parallel(int $n, callable $fn): void
# 参数
$n : 设置最大的协程数为 $n;
$fn : 对应需要执行的回调函数。
案例:
$startTIme = microtime(true);
Swoole\Coroutine\run(function () {
$use = microtime(true);
$results = [];
Swoole\Coroutine\parallel(2, function () use (&$results) {
Swoole\Coroutine\System::sleep(0.2);
$results[] = Swoole\Coroutine\System::gethostbyname('localhost');
});
$use = microtime(true) - $use;
echo '并发执行花费时间:' . $use . '秒' . PHP_EOL;
print_r($results);
});
$endTime = microtime(true) - $startTIme;
echo '花费时间 ' . $endTime . '秒' . PHP_EOL;
输出结果:
$php 31-swoole-coroutine-api.php
并发执行花费时间:0.20351409912109秒
Array
(
[0] => 127.0.0.1
[1] => 127.0.0.1
)
花费时间 0.20427894592285秒
函数 map
含义:类似 array_map
,为数组的每个元素应用回调函数。
语法:
Swoole\Coroutine\map(array $list, callable $fn, float $timeout = -1): array
# 参数
$list :运行 $fn 函数的数组;
$fn : $list 数组中的每个元素需要执行的回调函数;
$timeout : 总的超时时间,超时后会立即返回。但正在运行的协程会继续执行完毕,而不会中止。
案例:
function num(int $n): int {
return array_product(range($n, 1));
}
Swoole\Coroutine\run(function () {
$results = Swoole\Coroutine\map([4,5,6,7], 'num');
print_r($results);
});
输出结果:
$php 31-swoole-coroutine-api.php
Array
(
[0] => 24
[1] => 120
[2] => 720
[3] => 5040
)
函数:deadlock_check
含义:协程死锁检测,调用时会输出相关堆栈信息;
语法:Swoole\Coroutine\deadlock_check();
默认开启,在 EventLoop 终止后,若存在协程死锁,底层会自动调用。