Go context.WithCancel()的使用
WithCancel可以将一个Context包装为cancelCtx,并提供一个取消函数,调用这个取消函数,可以Cancel对应的Context
Go语言context包-cancelCtx
疑问
context.WithCancel()取消机制的理解
父母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:
// 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(可用于实现超时机制)
// 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(可用于实现超时机制)
回答我,停止 Goroutine 有几种方法?
golang context的done和cancel的理解 for循环channel实现context.Done()阻塞输出
更多关于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几次波动
本文由 mdnice 多平台发布
相关文章:

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

STM32 F103C8T6学习笔记6:IIC通信__驱动MPU6050 6轴运动处理组件—一阶互补滤波
今日主要学习一款倾角传感器——MPU6050,往后对单片机原理基础讲的会比较少,更倾向于简单粗暴地贴代码,因为经过前些日子对MSP432的学习,对原理方面也有些熟络了,除了在新接触它时会对其引脚、时钟、总线等进行仔细一些的研究之外…...

Ubantu安装Docker(完整详细)
先在官网上查看对应的版本:官网 然后根据官方文档一步一步跟着操作即可 必要准备 要成功安装Docker Desktop,必须: 满足系统要求 拥有64位版本的Ubuntu Jammy Jellyfish 22.04(LTS)或Ubuntu Impish Indri 21.10。 Docker Deskto…...

【从零开始学习JAVA | 第四十一篇】深入JAVA锁机制
目录 前言: 引入: 锁机制: CAS算法: 乐观锁与悲观锁: 总结: 前言: 在多线程编程中,线程之间的协作和资源共享是一个重要的话题。当多个线程同时操作共享数…...

Playable 动画系统
Playable 基本用法 Playable意思是可播放的,可运行的。Playable整体是树形结构,PlayableGraph相当于一个容器,所有元素都被包含在里面,图中的每个节点都是Playable,叶子节点的Playable包裹原始数据,相当于输…...

深入理解Linux内核--虚拟文件
虚拟文件系统(VFS)的作用 虚拟文件系统(Virtual Filesystem)也可以称之为虚拟文件系统转换(Virtual Filesystem Switch,VFS), 是一个内核软件层, 用来处理与Unix标准文件系统相关的所有系统调用。 其健壮性表现在能为各种文件系统提供一个通用的接口。VFS支持的文件…...
记一次 .NET 某外贸ERP 内存暴涨分析
一:背景 1. 讲故事 上周有位朋友找到我,说他的 API 被多次调用后出现了内存暴涨,让我帮忙看下是怎么回事?看样子是有些担心,但也不是特别担心,那既然找到我,就给他分析一下吧。 二࿱…...

关于安卓打包生成aar,jar实现(一)
关于安卓打包生成aar,jar方式 背景 在开发的过程中,主项目引入三方功能的方式有很多,主要是以下几个方面: (1)直接引入源代码module(优点:方便修改源码,易于维护&#…...
QString字符串与16进制QByteArray的转化,QByteArray16进制数字组合拼接,Qt16进制与10进制的转化
文章目录 QString转16进制QByteArry16进制QByteArray转QStringQByteArray16进制数拼接Qt16进制与10进制的转化在串口通信中,常常使用QByetArray储存数据,QByteArray可以看成字节数组,每个索引位置储存一个字节也就是8位的数据,可以储存两位16进制数,可以用uint8取其中的数…...

ElasticSearch安装与启动
ElasticSearch安装与启动 【服务端安装】 1.1、下载ES压缩包 目前ElasticSearch最新的版本是7.6.2(截止2020.4.1),我们选择6.8.1版本,建议使用JDK1.8及以上。 ElasticSearch分为Linux和Window版本,基于我们主要学习…...

JavaWeb中Json传参的条件
JavaWeb中我们常用json进行参数传递 对应的注释为RequestBody 但是json传参是有条件的 最主要是你指定的实体类和对应的json参数能否匹配 1.属性和对应的json参数名称对应 2.对应实体类实现了Serializable接口,可以进行序列化和反序列化,这个才是实体类转…...

包装类+初识泛型
目录 1 .包装类 1.1 基本数据类型对应的包装类 1.2.1装箱 1.2.2拆箱 2.初识泛型 2.1什么是泛型 2.2泛型类 2.3裸类型 2.4泛型的上界 2.5泛型方法 1 .包装类 基本数据类型所对应的类类型 在 Java 中,由于基本类型不是继承自 Object ,为了在泛型…...
基于改进的长短期神经网络电池电容预测,基于DBN+LSTM+SVM的电池电容预测
目录 背影 摘要 LSTM的基本定义 LSTM实现的步骤 基于长短期神经网络LSTM的客电池电容预测 完整代码: 基于长短期神经网络LSTM的公交站客流量预测资源-CSDN文库 https://download.csdn.net/download/abc991835105/88184734 效果图 结果分析 展望 参考论文 背影 为增加电动车行…...
Python 2.x 中如何使用pandas模块进行数据分析
Python 2.x 中如何使用pandas模块进行数据分析 概述: 在数据分析和数据处理过程中,pandas是一个非常强大且常用的Python库。它提供了数据结构和数据分析工具,可以实现快速高效的数据处理和分析。本文将介绍如何在Python 2.x中使用pandas进行数据分析&am…...

获取Spring中bean工具类
获取Spring中bean工具类 工具类 package com.geekmice.springbootselfexercise.utils;import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org…...
【实战篇】亿级高并发电商项目(新建 ego_pojo、ego_mapper、ego_api、ego_provider、搭建后台项目 )十五
目录 八、 搭建 Provide 1 新建 ego_pojo 2 新建 ego_mapper 2.1编写 pom.xml 2.2新建配置文件 编辑...

【Plex】FRP内网穿透后 App无法使用问题
能搜索到这个文章的,应该都看过这位同学的分析【Plex】FRP内网穿透后 App无法使用问题_plex frp无效_Fu1co的博客-CSDN博客 这个是必要的过程,但是设置之后仍然app端无法访问,原因是因为网络端口的问题 这个里面的这个公开端口,可…...

[管理与领导-11]:IT基层管理者 - 目标与落实 - 过程管理失控,结果总难达成的问题思考:如何把过程管控做得更好?
目录 前言: 第1章 问题与现象 1.1 总有意想不到的事发生:意外事件 1.2 总有计划变更:意外影响 1.3 总有一错再错,没有复盘、总结与反思,没有流程与改进 第2章 背后的原因 2.1 缺乏及时的过程检查 - 缺乏异常检测…...
用php语言写一个chatgpt3.5模型的例子
当然可以!使用PHP语言调用OpenAI API与ChatGPT-3.5模型进行交互。首先,确保你已经安装了PHP 7.2或更新版本,并具备可用的OpenAI API密钥。 下面是一个基本的PHP示例,展示了如何使用OpenAI API与ChatGPT-3.5模型进行对话ÿ…...

PHP实现保质期计算器
1.php实现保质期计算, 保质期日期可选,天 、月、年 2. laravel示例 /*** 保质期计算器* return void*/public function expirationDateCal(){$produce_date $this->request(produce_date); // 生产日期$warranty_date $this->reques…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...