Home
Download
Document
Forum
Video
Donate
Source Code
AI 助理
Sponsors
CRMEB
Apipost
腾讯云
微擎
禅道
51Talk
Products
Swoole AI 智能文档翻译器
Swoole-Compiler PHP 代码加密器
CRMEB 新零售社交电商系统
Login
Register
全部
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
发表新帖
Swoole v4.7 版本新特性预览之 Co::cancel()
相信之前就有很多用户想要一个取消协程的 API,迟迟没有添加进来,现在在 v4.7 版本中进行了添加: 具体实现见:[#4247](https://github.com/swoole/swoole-src/pull/4247) ,[#4249](https://github.com/swoole/swoole-src/pull/4249) ## 新增 API & 常量 新增了两个 API,分别为 ```php Co::cancel($cid): bool ``` > 用于取消某个协程,但不能对当前协程发起取消操作 和 ```php 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()`进行取消,这时是不能取消的;同时协程容器中只有一个协程,去取消一个不存在的协程也是不可以的。 ```php 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 ```php 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 ```php 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 ```php 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()` 判断是不是被取消。 ![](https://cdn.jsdelivr.net/gh/sy-records/staticfile/images/swoole/wechat.png)
发布于3年前 · 35 次浏览 · 来自
分享
鲁飞
相信之前就有很多用户想要一个取消协程的 API,迟迟没有添加进来,现在在 v4.7 版本中进行了添加: 具体实现见:[#4247](https://github.com/swoole/swoole-src/pull/4247) ,[#4249](https://github.com/swoole/swoole-src/pull/4249) ## 新增 API & 常量 新增了两个 API,分别为 ```php Co::cancel($cid): bool ``` > 用于取消某个协程,但不能对当前协程发起取消操作 和 ```php 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()`进行取消,这时是不能取消的;同时协程容器中只有一个协程,去取消一个不存在的协程也是不可以的。 ```php 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 ```php 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 ```php 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 ```php 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()` 判断是不是被取消。 ![](https://cdn.jsdelivr.net/gh/sy-records/staticfile/images/swoole/wechat.png)
赞
2
分享
收藏
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
评论
2021-06-13
MARiA
请问为啥被 CPU 中断调度器强制切换的协程不能取消呢
赞
0
回复
微信公众号
热门内容
作者其它话题
- thinkphp5.1在使用think-swoole的时候报错unsupported option [host]
- WSL 下服务器响应数据过大无法接收
暂无回复的问答
- CodeGalaxy K3s 轻量集群节点之间如何实现负载均衡
- 关于openssl CURL WARNING swSSL_connect: SSL_connect(fd=69) failed. Error: error:141A318A:SSL routines:tls_process_ske_dhe:dh key too small[1|394]
- 多个模型如何进行事务异常回退?
- websocket开启wss报错
- 协程tcp服务器如何使用多进程?recv()方法接收信息,打印出来的pid一直是同一个。没用使用到多进程啊。