置信之前就有很多用户想要一个勾销协程的 API,迟迟没有增加进来,当初在 v4.7 版本中进行了增加:

具体实现见:#4247 ,#4249

新增 API & 常量

新增了两个 API,别离为

Co::cancel($cid): bool
用于勾销某个协程,但不能对以后协程发动勾销操作

Co::isCanceled(): bool
用于判断以后协程是不是被勾销的

新增了三个错误码:

常量含意
SWOOLE_ERROR_CO_CANNOT_CANCEL协程不能取消
SWOOLE_ERROR_CO_NOT_EXISTS协程不存在
SWOOLE_ERROR_CO_CANCELED协程已被勾销

阐明

该 API 用于从一个协程或者事件回调中勾销另外一个协程。

只有处于可勾销操作中的协程能力被勾销, 当胜利勾销一个协程时, 上下文环境将会立刻切换到对应协程中

尝试勾销一个处于不可勾销操作中的协程, Co::cancel()胜利时返回 true,失败将会返回false,

此时调用swoole_last_error(),可能有两种状况:

  1. 协程不存在 SWOOLE_ERROR_CO_NOT_EXISTS
  2. 协程处于不可勾销的状态 SWOOLE_ERROR_CO_CANNOT_CANCEL

能够通过Co::isCanceled()来判断以后操作是否是被手动勾销的, 手动勾销失常完结, 将返回true, 如失败, 将返回false

目前根本反对了绝大部分的协程 API 的勾销,包含:

  1. socket
  2. AsyncIO (fread, gethostbyname ...)
  3. sleep
  4. waitSignal
  5. wait/waitpid
  6. waitEvent
  7. Co::suspend/Co::yield
  8. channel
  9. native curl (SWOOLE_HOOK_NATIVE_CURL)

有两个不可中断的场景

  1. 被 CPU 中断调度器强制切换的协程
  2. 文件锁操作期间
不过,可能在后续版本也会容许进行勾销,敬请期待

应用场景

基于协程勾销这一性能,能够在用户侧实现:

  • 基于协程粒度的超时熔断

在之前的版本中已挂起的协程是不可被动调度的,而Co::cancel()Co::resume()的区别就是,不止能够勾销手动Co::yield()的协程,能够勾销所有容许勾销的协程。

  • 更好的 API 设计

和传统 PHP 的相似性能的 API 不同的是, Swoole 中大量的 API 减少了 timeout 参数, 当然也有局部难以增加或者说不适合增加 timeout 参数的, 比方文件操作系列函数, 当初所有都有了可能, 能够在 PHP 层实现任意 IO 操作的超时, 而无需依赖于底层的 API 设计

示例

上面来看一些示例代码,理解一下协程勾销的用法:

不能对以后协程以及不存在的协程发动勾销操作

在协程容器中主动创立了一个协程,就调用Co::cancel()进行勾销,这时是不能取消的;同时协程容器中只有一个协程,去勾销一个不存在的协程也是不能够的。

use Swoole\Coroutine;use function Swoole\Coroutine\run;run(function () {    assert(Coroutine::cancel(Coroutine::getCid()) === false);    assert(swoole_last_error() === SWOOLE_ERROR_CO_CANNOT_CANCEL);    assert(Coroutine::cancel(999) === false);    assert(swoole_last_error() === SWOOLE_ERROR_CO_NOT_EXISTS);});

以下三个示例别离演示了在Co::suspend/Co::yieldAsyncIOchannel中应用sleep来伪造timeout后进行勾销

Co::suspend/Co::yield

use Swoole\Coroutine;use Swoole\Coroutine\System;use function Swoole\Coroutine\run;use function Swoole\Coroutine\go;run(function () {    $cid = Coroutine::getCid();    go(function () use ($cid) {        System::sleep(0.002);        assert(Coroutine::cancel($cid) === true);    });    $retval = Coroutine::suspend();    echo "Done\n";    assert($retval === false);    assert(swoole_last_error() === SWOOLE_ERROR_CO_CANCELED);});

AsyncIO

use Swoole\Coroutine;use Swoole\Event;use Swoole\Coroutine\System;use function Swoole\Coroutine\run;run(function () {    $cid = Coroutine::getCid();    Event::defer(function () use ($cid) {        assert(Coroutine::cancel($cid) === true);    });    $retval = System::gethostbyname('www.baidu.com');    echo "Done\n";    assert($retval === false);    assert(swoole_last_error() === SWOOLE_ERROR_AIO_CANCELED);});

channel

use Swoole\Coroutine;use Swoole\Coroutine\System;use function Swoole\Coroutine\run;use function Swoole\Coroutine\go;run(function () {    $chan = new Coroutine\Channel(1);    $cid = Coroutine::getCid();    go(function () use ($cid) {        System::sleep(0.002);        assert(Coroutine::cancel($cid) === true);    });    assert($chan->push("hello world [1]", 100) === true);    assert(Coroutine::isCanceled() === false);    assert($chan->errCode === SWOOLE_CHANNEL_OK);    assert($chan->push("hello world [2]", 100) === false);    assert(Coroutine::isCanceled() === true);    assert($chan->errCode === SWOOLE_CHANNEL_CANCELED);    echo "Done\n";});

当内部应用 Co::cancel() 勾销一个协程的挂起状态时,该协程所调用的 API 会立刻返回失败,程序代码会持续向下执行。

通过判断协程操作函数/办法返回值和错误码,或者应用 Co::isCanceled() 判断是不是被勾销。