共计 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()
,可能有两种状况:
- 协程不存在
SWOOLE_ERROR_CO_NOT_EXISTS
- 协程处于不可勾销的状态
SWOOLE_ERROR_CO_CANNOT_CANCEL
能够通过 Co::isCanceled()
来判断以后操作是否是被手动勾销的, 手动勾销失常完结, 将返回true
, 如失败, 将返回false
目前根本反对了绝大部分的协程 API 的勾销,包含:
- socket
- AsyncIO (fread, gethostbyname …)
- sleep
- waitSignal
- wait/waitpid
- waitEvent
- Co::suspend/Co::yield
- channel
- native curl (SWOOLE_HOOK_NATIVE_CURL)
有两个不可中断的场景
- 被 CPU 中断调度器强制切换的协程
- 文件锁操作期间
不过,可能在后续版本也会容许进行勾销,敬请期待
应用场景
基于协程勾销这一性能,能够在用户侧实现:
- 基于协程粒度的超时熔断
在之前的版本中已挂起的协程是不可被动调度的,而 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::yield
、AsyncIO
和channel
中应用 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()
判断是不是被勾销。