关于php:Swoole-v47-版本新特性预览之-Cocancel

3次阅读

共计 2936 个字符,预计需要花费 8 分钟才能阅读完成。

置信之前就有很多用户想要一个勾销协程的 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() 判断是不是被勾销。

正文完
 0