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…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
【第二十一章 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 数据流…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
实现p2p的webrtc-srs版本
1. 基本知识 1.1 webrtc 一、WebRTC的本质:实时通信的“网络协议栈”类比 将WebRTC类比为Linux网络协议栈极具洞察力,二者在架构设计和功能定位上高度相似: 分层协议栈架构 Linux网络协议栈:从底层物理层到应用层(如…...
Nginx 事件驱动理解
在做埋点采集服务的过程中,主要依靠openresty加lua脚本来实现采集。高并发还是主要依靠nginx来实现。而其核心就是事件驱动/多路io复用(epoll机制),不同的linux服务器都有对应的实现方式。 而epoll机制就是,应用启动的…...
