8

Swoole v4.7 版本新特性预览之 Co::cancel()

 2 years ago
source link: https://segmentfault.com/a/1190000040139563
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK