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 …...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...