三十一、Swoole 基础学习笔记 - Swoole 协程核心 API

作者: 温新

分类: 【Swoole 系列】

阅读: 1865

时间: 2023-03-13 12:15:29

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 版本中底层会分配 8Kstack 来存储协程的变量,zval 的尺寸为 16字节,因此 8Kstack 最大可以保存 512 个变量。协程栈内存占用超过 8KZendVM 会自动扩容。

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 终止后,若存在协程死锁,底层会自动调用。

请登录后再评论