context.WithCancel()的使用
“WithCancel可以将一个Context包装为cancelCtx,并提供一个取消函数,调用这个取消函数,可以Cancel对应的Context
Go语言context包-cancelCtx[1]
疑问
context.WithCancel()取消机制的理解[2]
父母5s钟后出门,倒计时,父母在时要学习,父母一走就可以玩
package main
import (
"context"
"fmt"
"time"
)
func dosomething(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("playing")
return
default:
fmt.Println("I am working!")
time.Sleep(time.Second)
}
}
}
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
go func() {
time.Sleep(5 * time.Second)
cancelFunc()
}()
dosomething(ctx)
}

为什么调用cancelFunc就能从ctx.Done()里取得返回值? 进而取消对应的Context?
复习一下channel的一个特性
从一个已经关闭的channel里可以一直获取对应的零值

WithCancel代码分析
pkg.go.dev/context#WithCancel:[3]
// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
//WithCancel 返回具有新 Done 通道的 parent 副本。 返回的上下文的完成通道在调用返回的取消函数或父上下文的完成通道关闭时关闭,以先发生者为准。
//取消此上下文会释放与其关联的资源,因此代码应在此上下文中运行的操作完成后立即调用取消。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent) // 将parent作为父节点context 生成一个新的子节点
//获得“父Ctx路径”中可被取消的Ctx
//将child canceler加入该父Ctx的map中
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
WithCancel最后返回 子上下文和一个cancelFunc函数,而cancelFunc函数里调用了cancelCtx这个结构体的方法cancel
(代码基于go 1.16; 1.17有所改动)
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call done是一个channel,用来 传递关闭信号
children map[canceler]struct{} // set to nil by the first cancel call children是一个map,存储了当前context节点下的子节点
err error // set to non-nil by the first cancel call err用于存储错误信息 表示任务结束的原因
}
在cancelCtx这个结构体中,字段done是一个传递空结构体类型的channel,用来在上下文取消时关闭这个通道,err就是在上下文被取消时告诉用户这个上下文取消了,可以用ctx.Err()来获取信息
canceler是一个实现接口,用于Ctx的终止。实现该接口的Context有cancelCtx和timerCtx,而emptyCtx和valueCtx没有实现该接口。

// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})
func init() {
close(closedchan)
}
// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
/**
* 1、cancel(...)当前Ctx的子节点
* 2、从父节点中移除该Ctx
**/
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
// 设置取消原因
c.err = err
// 设置一个关闭的channel或者将done channel关闭,用以发送关闭信号
if c.done == nil {
c.done = closedchan
} else {
close(c.done) // 注意这一步
}
// 将子节点context依次取消
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
// 将当前context节点从父节点上移除
removeChild(c.Context, c)
}
}
对于cancel函数,其取消了基于该上下文的所有子上下文以及把自身从父上下文中取消
对于更多removeFromParent
代码分析,和其他Context的使用,强烈建议阅读 深入理解Golang之Context(可用于实现超时机制)[4]
// Done is provided for use in select statements:
//
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See https://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancellation.
Done() <-chan struct{}
// If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error explaining why:
// Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed.
// After Err returns a non-nil error, successive calls to Err return the same error.
Err() error
当调用cancelFunc()
时,会有一步close(d)
的操作,
ctx.Done 获取一个只读的 channel,类型为结构体。可用于监听当前 channel 是否已经被关闭。
Done()用来监听cancel操作(对于cancelCtx)或超时操作(对于timerCtx),当执行取消操作或超时时,c.done会被close,这样就能从一个已经关闭的channel里一直获取对应的零值,<-ctx.Done
便不会再阻塞
(代码基于go 1.16; 1.17有所改动)
func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
总结一下:使用context.WithCancel
时,除了返回一个新的context.Context(上下文),还会返回一个cancelFunc
。 在需要取消该context.Context时,就调用这个cancelFunc
,之后当前上下文及其子上下文都会被取消,所有的 Goroutine 都会同步收到这一取消信号
至于cancelFunc
是如何做到的?
在用户代码,for循环里select不断尝试从 <-ctx.Done()里读取出内容,但此时并没有任何给 c.done这个channel写入数据的操作,(类似c.done <- struct{}{}
),故而在for循环里每次select时,这个case都不满足条件,一直阻塞着。每次都执行default代码段
而在执行cancelFunc
时, 在func (c *cancelCtx) cancel(removeFromParent bool, err error)
里面,会有一个close(c.done)
的操作。而从一个已经关闭的channel里可以一直获取对应的零值,即 select可以命中,进入case res := <-ctx.Done():
代码段
可用如下代码验证:
package main
import (
"context"
"fmt"
"time"
)
func dosomething(ctx context.Context) {
var cuiChan = make(chan struct{})
go func() {
cuiChan <- struct{}{}
}()
//close(cuiChan)
for {
select {
case res := <-ctx.Done():
fmt.Println("res:", res)
return
case res2 := <-cuiChan:
fmt.Println("res2:", res2)
default:
fmt.Println("I am working!")
time.Sleep(time.Second)
}
}
}
func main() {
test()
ctx, cancelFunc := context.WithCancel(context.Background())
go func() {
time.Sleep(5 * time.Second)
cancelFunc()
}()
dosomething(ctx)
}
func test() {
var testChan = make(chan struct{})
if testChan == nil {
fmt.Println("make(chan struct{})后为nil")
} else {
fmt.Println("make(chan struct{})后不为nil!!!")
}
}
输出:
make(chan struct{})后不为nil!!!
I am working!
res2: {}
I am working!
I am working!
I am working!
I am working!
res: {}
而如果 不向没有缓存的cuiChan写入数据,直接close,即
package main
import (
"context"
"fmt"
"time"
)
func dosomething(ctx context.Context) {
var cuiChan = make(chan struct{})
//go func() {
// cuiChan <- struct{}{}
//}()
close(cuiChan)
for {
select {
case res := <-ctx.Done():
fmt.Println("res:", res)
return
case res2 := <-cuiChan:
fmt.Println("res2:", res2)
default:
fmt.Println("I am working!")
time.Sleep(time.Second)
}
}
}
func main() {
test()
ctx, cancelFunc := context.WithCancel(context.Background())
go func() {
time.Sleep(5 * time.Second)
cancelFunc()
}()
dosomething(ctx)
}
func test() {
var testChan = make(chan struct{})
if testChan == nil {
fmt.Println("make(chan struct{})后为nil")
} else {
fmt.Println("make(chan struct{})后不为nil!!!")
}
}
则会一直命中case 2
res2: {}
res2: {}
res2: {}
res2: {}
res2: {}
res2: {}
res2: {}
...
//一直打印下去
更多参考:
深入理解Golang之Context(可用于实现超时机制)[5]
回答我,停止 Goroutine 有几种方法?
golang context的done和cancel的理解 for循环channel实现context.Done()阻塞输出[6]
更多关于channel阻塞与close的代码
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string, 0)
go func() {
for {
fmt.Println("----开始----")
v, ok := <-ch
fmt.Println("v,ok", v, ok)
if !ok {
fmt.Println("结束")
return
}
//fmt.Println(v)
}
}()
fmt.Println("<-ch一直没有东西写进去,会一直阻塞着,直到3秒钟后")
fmt.Println()
fmt.Println()
time.Sleep(3 * time.Second)
ch <- "向ch这个channel写入第一条数据..."
ch <- "向ch这个channel写入第二条数据!!!"
close(ch) // 当channel被close后, v,ok 中的ok就会变为false
time.Sleep(10 * time.Second)
}
输出为:
----开始----
<-ch一直没有东西写进去,会一直阻塞着,直到3秒钟后
v,ok 向ch这个channel写入第一条数据... true
----开始----
v,ok 向ch这个channel写入第二条数据!!! true
----开始----
v,ok false
结束
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
ch := make(chan string, 0)
done := make(chan struct{})
go func() {
var i int32
for {
atomic.AddInt32(&i, 1)
select {
case ch <- fmt.Sprintf("%s%d%s", "第", i, "次向通道中写入数据"):
case <-done:
close(ch)
return
}
// select随机选择满足条件的case,并不按顺序,所以打印出的结果,在30几次波动
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
time.Sleep(3 * time.Second)
done <- struct{}{}
}()
for i := range ch {
fmt.Println("接收到的值: ", i)
}
fmt.Println("结束")
}
输出为:
接收到的值: 第1次向通道中写入数据
接收到的值: 第2次向通道中写入数据
接收到的值: 第3次向通道中写入数据
接收到的值: 第4次向通道中写入数据
接收到的值: 第5次向通道中写入数据
接收到的值: 第6次向通道中写入数据
接收到的值: 第7次向通道中写入数据
接收到的值: 第8次向通道中写入数据
接收到的值: 第9次向通道中写入数据
接收到的值: 第10次向通道中写入数据
接收到的值: 第11次向通道中写入数据
接收到的值: 第12次向通道中写入数据
接收到的值: 第13次向通道中写入数据
接收到的值: 第14次向通道中写入数据
接收到的值: 第15次向通道中写入数据
接收到的值: 第16次向通道中写入数据
接收到的值: 第17次向通道中写入数据
接收到的值: 第18次向通道中写入数据
接收到的值: 第19次向通道中写入数据
接收到的值: 第20次向通道中写入数据
接收到的值: 第21次向通道中写入数据
接收到的值: 第22次向通道中写入数据
接收到的值: 第23次向通道中写入数据
接收到的值: 第24次向通道中写入数据
接收到的值: 第25次向通道中写入数据
接收到的值: 第26次向通道中写入数据
接收到的值: 第27次向通道中写入数据
接收到的值: 第28次向通道中写入数据
接收到的值: 第29次向通道中写入数据
接收到的值: 第30次向通道中写入数据
接收到的值: 第31次向通道中写入数据
结束
每次执行,打印出的结果,在30几次波动
参考资料
Go语言context包-cancelCtx: https://dashen.tech/2019/06/23/Go%E8%AF%AD%E8%A8%80context%E5%8C%85/#cancelCtx
[2]context.WithCancel()取消机制的理解: https://blog.csdn.net/weixin_42216109/article/details/123694275
[3]pkg.go.dev/context#WithCancel:: https://pkg.go.dev/context#WithCancel
[4]深入理解Golang之Context(可用于实现超时机制): https://blog.csdn.net/qq_25821689/article/details/105850717
[5]深入理解Golang之Context(可用于实现超时机制): https://blog.csdn.net/qq_25821689/article/details/105850717
[6]golang context的done和cancel的理解 for循环channel实现context.Done()阻塞输出: https://blog.csdn.net/nakeer/article/details/120897267
本文由 mdnice 多平台发布
相关文章:

context.WithCancel()的使用
“ WithCancel可以将一个Context包装为cancelCtx,并提供一个取消函数,调用这个取消函数,可以Cancel对应的Context Go语言context包-cancelCtx[1] 疑问 context.WithCancel()取消机制的理解[2] 父母5s钟后出门,倒计时,父母在时要学习,父母一走…...

vue3中引入百度地图
话不多说直接开干 1.第一种方式 百度地图地址 打开 https://lbsyun.baidu.com/index.php?title%E9%A6%96%E9%A1%B5 然后点进去地图 然后再这个功能里面选择一个地图,然后跳转页面 然后一直下滑 滑到底部 点击这个 跳转到这个页面 然后点击进入demo这个 然后到这个…...

【Linux-Day8- 进程替换和信号】
进程替换和信号 问题引入 我们发现 终端输入的任意命令的父进程都是bash,这是因为Linux系统是用fork()复制出子进程,然后在子进程中调用替换函数进行进程替换,实现相关命令。 (1) exec 系列替换过程:pcb 使用以前的只…...
日志文件之间关系和介绍及应用
1.常用日志框架代码举例 Log4j: Log4j是Java中广泛使用的日志框架之一。它提供了灵活的配置选项和丰富的功能,支持日志级别、日志输出目标等。Log4j有1.x版本和2.x版本,其中Log4j 2.x是对1.x的升级和扩展。 Logback: Logback是由Log4j创始人设计的Log4…...

mac电脑屏幕录制Berrycast Mac屏幕录制软件
Berrycast是一款为Mac设计的优秀屏幕录制软件,它让屏幕录制变得简单而高效。以下是Berrycast的一些主要特点: 简单的用户界面:Berrycast拥有直观和简洁的用户界面,使得用户可以轻松上手。高质量的视频输出:Berrycast能…...
机器学习笔记之最优化理论与方法(一)最优化问题概述
机器学习笔记之最优化理论与方法——最优化问题概述 引言什么是最优化问题最优化问题的基本形式最优化问题的分类各分类最优化问题的数学表达约束优化VS无约束优化线性规划VS非线性规划连续优化VS离散优化单目标优化VS多目标优化 引言 从本节开始,将对最优化理论与…...
【ES5新特性一】 严格模式语法变化、全局的JSON对象、编码和解码的方法
前言 ECMAScript 和 JavaScript 的关系 一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系? 要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准…...

Java【手撕滑动窗口】LeetCode 3. “无重复字符的最长子串“, 图文详解思路分析 + 代码
文章目录 前言一、长度最小子数组1, 题目2, 思路分析3, 代码 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: 📕 JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等 📗 Java数据结构: 顺序表, 链…...
学习哈哈哈哈
# 零、学习计划 * 数据库相关 * 索引 * [我以为我对数据库索引很了解,直到我遇到了阿里面试官 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/107487215) * [给我一分钟,让你彻底明白MySQL聚簇索引和非聚簇索引 - 知乎 (zhihu.com)](ht…...

05-基础例程5
基础例程5 1、超声波测距 实验介绍 HC-SR04超声波传感器是一款测量距离的传感器。其原理是利用声波在遇到障碍物反射接收结合声波在空气中传播的速度计算的得出。 外观 管脚功能的定义 VCC:供电电源;Trig:触发信号;Echo&a…...

双基证券:预计未来还会有更多政策来吸引增量资金
双基证券表示,8月27日,活泼资本商场五大方针出台:证券交易印花税折半征收;阶段性收紧IPO节奏;上市房企再融资不受破发、破净和亏本限制;标准控股股东与实际操控人减持行为;融资保证金最低份额由…...

前端:html实现页面切换、顶部标签栏,类似于浏览器的顶部标签栏(完整版)
效果 代码 <!DOCTYPE html> <html><head><style>/* 左侧超链接列表 */.link {display: block;padding: 8px;background-color: #f2f2f2;cursor: pointer;}/* 顶部标签栏 */#tabsContainer {width:98%;display: flex;align-items: center;overflow-x: …...

强化自主可控,润开鸿发布基于RISC-V架构的开源鸿蒙终端新品
2023 RISC-V中国峰会于8月23日至25日在北京召开,峰会以“RISC-V生态共建”为主题,结合当下全球新形势,把握全球新时机,呈现RISC-V全球新观点、新趋势。本次大会邀请了RISC-V国际基金会、业界专家、企业代表及社区伙伴等共同探讨RISC-V发展趋势与机遇,吸引超过百余家业界企业、高…...
软件设计师知识点·1
控制器: (1)指令寄存器(IR) : CPU执行一条指令时,从内存储器取到缓冲寄存器中,再送入IR暂存; (2)程序计数器(PC): 将要执行的下一条指令的地址; (3)地址寄存器(IR): 当前CPU所访问的内存单元地址; (4)指令译码器(ID): 对指令中的操作码字段进行分析解释; 多核CPU可以满足用户…...

修改Jupyter Notebook默认打开路径
这里我是重新下载的anaconda,打开Jupyter之后是默认在C盘的一个路径的,现在我们就来修改一下它的一个默认打开路径,这样在我们后续学习过程中,可以将ipynb后缀的文件放在这个目录下就能查看了。 1、先打开Anaconda Prompt&#x…...

经典卷积网络
目录 一、经典神经网络出现的时间线编辑 二、LeNet 三、AlexNet 四、VGGNet 五、InceptionNet 六、ResNet 总结: 一、经典神经网络出现的时间线 二、LeNet 背景:LeNet由Yann LeCun于1998年提出,卷积网络开篇之作。 解释࿱…...
react+koa+vite前后端模拟jwt鉴权过程
路由组件(生成token) const Router require(koa/router) const jwt require(jsonwebtoken); const router new Router()const mockDbUserInfo [{nickname: xxxliu,username: Tom,password: 123456,icon: url1},{nickname: xxx,username: John,passw…...

VK1616是LED显示控制驱动电路/LED驱动IC、数显驱动芯片、数码管驱动芯片
产品品牌:永嘉微电/VINKA 产品型号:VK1616 封装形式:SOP16 产品年份:新年份 概述:VK1616是一种数码管或点阵LED驱动控制专用芯片,内部集成有3线串行接口、数据锁存器、LED 驱动等电路。SEG脚接LED阳极&a…...

开箱报告,Simulink Toolbox库模块使用指南(五)——S-Fuction模块(C MEX S-Function)
文章目录 前言 C MEX S-Function 算法原理 原始信号创建 编写S函数 仿真验证 Tips 分析和应用 总结 前言 见《开箱报告,Simulink Toolbox库模块使用指南(一)——powergui模块》 见《开箱报告,Simulink Toolbox库模块使用…...

摄像头的调用和视频识别
CV_tutorial3 摄像头调用实时播放保存视频 运动目标识别帧差法背景减除法 摄像头调用 创建视频捕捉对象:cv2.VideoCapture() 参数为视频设备的索引号,就一个摄像投的话写0默认; 或者是指定要读取视频的路径。 实时播放 import cv2 import …...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...