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)
}
alt

为什么调用cancelFunc就能从ctx.Done()里取得返回值? 进而取消对应的Context?


复习一下channel的一个特性


从一个已经关闭的channel里可以一直获取对应的零值

alt

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没有实现该接口。

alt
// 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 string0)
 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 string0)
 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 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/64697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

策略模式(Strategy)

策略模式是一种行为设计模式&#xff0c;就是定义一系列算法&#xff0c;然后将每一个算法封装起来&#xff0c;并使它们可相互替换。本模式通过定义一组可相互替换的算法&#xff0c;实现将算法独立于使用它的用户而变化。 Strategy is a behavioral design pattern that def…

机器学习笔记

文章目录 编码器-解码器Batch Normalization好处 编码器-解码器 第二个input与transformer中的解码器类似。 Batch Normalization 尽量使得w1和w2之间呈现为正圆 训练模型的时候&#xff0c; μ \mu μ和 σ \sigma σ不可以认为是常数&#xff0c;而是包含数据的变量&…

数据库优化:探索 SQL 中的索引

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 在一本书中搜索特定主题时&#xff0c;我们将首先访问索引页面&#xff08;该页面位于该书的开头&#xff09;&#xff0c;并找到包含我们感兴趣的主题的页码。现在&#xff0c;想象一下在没有索引页的书中…

pycharm离线安装依赖包

一、对于单个下载离线包&#xff0c;然后安装 1、先去https://pypi.org/网站下载离线包&#xff0c;下载到本地&#xff1b; 2、从磁盘中找到刚刚下载包&#xff0c;点击确定就可以安装了 二、将本地项目所有依赖包全部下载下来&#xff0c;然后批量在另一个项目&#xff…

Pytorch Tutorial【Chapter 2. Autograd】

Pytorch Tutorial 文章目录 Pytorch TutorialChapter 2. Autograd1. Review Matrix Calculus1.1 Definition向量对向量求导1.2 Definition标量对向量求导1.3 Definition标量对矩阵求导 2.关于autograd的说明3. grad的计算3.1 Manual手动计算3.2 backward()自动计算 Reference C…

ParallelCollectionRDD [0] isEmpty at KyuubiSparkUtil.scala:48问题解决

ParallelCollectionRDD [0] isEmpty at KyuubiSparkUtil.scala:48问题解决 这个问题出现在使用Kyubi Spark Util处理ParallelCollectionRDD的过程中&#xff0c;具体是在KyubiSparkUtil.scala文件的第48行调用isEmpty方法时出现的。该问题可能是由以下几个原因引起的&#xff1…

springboot(4)

AOP 1.AOP与OOP OOP&#xff08;Object Oriented Programming&#xff0c;面向对象编程&#xff09; AOP&#xff08;Aspect Oriented Programming&#xff0c;面向切面编程&#xff09; POP&#xff08;Process Oriented Programming&#xff0c;面向过程编程&#xff09; …

IPC之二:使用命名管道(FIFO)进行进程间通信的例子

IPC 是 Linux 编程中一个重要的概念&#xff0c;IPC 有多种方式&#xff0c;本文主要介绍命名管道(FIFO)&#xff0c;命名管道可以完成同一台计算机上的进程之间的通信&#xff0c;本文给出了多个具体的实例&#xff0c;每个实例均附有完整的源代码&#xff1b;本文所有实例在 …

Pytest测试框架搭建的关键6个知识点(建议收藏)

在现代软件开发中&#xff0c;测试是确保代码质量和功能稳定性的关键步骤。而Pytest作为一个功能强大且易于使用的Python测试框架&#xff0c;为我们提供了一个优雅的方式来编写和管理测试。本文将为你介绍如何构建高效可靠的测试环境&#xff0c;着重探讨Pytest测试框架搭建时…

java 版本企业招标投标管理系统源码+多个行业+tbms+及时准确+全程电子化tbms

​ 功能描述 1、门户管理&#xff1a;所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含&#xff1a;招标公告、非招标公告、系统通知、政策法规。 2、立项管理&#xff1a;企业用户可对需要采购的项目进行立项申请&#xff0c;并提交审批&#xff0c;查…

数据结构—树和二叉树

5.树和二叉树 5.1树和二叉树的定义 树形结构&#xff08;非线性结构&#xff09;&#xff1a;结点之间有分支&#xff0c;具有层次关系。 5.1.1树的定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个结点的有限集。 若n0&#xff0c;称为空树&#x…

文件传输软件的市场现状和未来趋势

文件传输软件是一种能够在不同计算机之间高效、便捷、安全地传送各种类型的文件的应用软件。它是计算机领域中的一项重要技术&#xff0c;涉及到网络通信、数据加密、文件管理等多个方面。随着互联网和移动互联网的发展&#xff0c;文件传输软件的市场需求也日益增大&#xff0…

Linux 中使用 verdaccio 搭建私有npm 服务器

安装 Node Linux中安装Node 安装verdaccio npm i -g verdaccio安装完成 输入verdaccio,出现下面信息代表安装成功&#xff0c;同时输入verdaccio后verdaccio已经处于运行状态&#xff0c;当然这种启动时暂时的&#xff0c;我们需要通过pm2让verdaccio服务常驻 ygiZ2zec61wsg…

iPhone苹果手机地震预警功能怎么开启?

iPhone苹果手机地震预警功能怎么开启&#xff1f; 1、打开iPhone苹果手机设置&#xff1b; 2、在iPhone苹果手机设置内找到辅助功能&#xff1b; 3、在辅助功能内找到触控&#xff1b; 4、在iPhone苹果手机辅助功能触控内找到振动&#xff0c;如果是关闭状态请启&#xff1b; …

看门狗文章

1. iwdg.c #include "stm32f4xx.h" #include "iwdg.h"//prer&#xff1a;预分频值 //rlr&#xff1a;自动重装载值 void IWDG_Init(unsigned char prer,unsigned int rlr)//IWDG初始化 {IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);…

你知道音频文件格式转换要用什么软件吗?今天就来分享给你

你是否曾经遇到过这样的情况&#xff1f;你正在享受着一首心爱的歌曲&#xff0c;却突然发现它的音频格式不兼容你的设备或播放器&#xff1f;或者你需要将录音文件从一种格式转换成另一种&#xff0c;以便在不同场景中使用&#xff1f;音频格式转换软件就像是一个通向音乐自由…

林大数据结构【2019】

关键字&#xff1a; 哈夫曼树权值最小、哈夫曼编码、邻接矩阵时间复杂度、二叉树后序遍历、二叉排序树最差时间复杂度、非连通无向图顶点数&#xff08;完全图&#xff09;、带双亲的孩子链表、平衡二叉树调整、AOE网关键路径 一、判断 二、单选 三、填空 四、应用题 五、算…

Windows搭建Snort环境及使用方式

目录 0x01 前置环境0x02修改配置文件0x03 自测0x04 使用0x05 感言 0x01 前置环境 环境描述windows10snort2.9.2https://www.snort.org/downloads 先把上面环境下载好&#xff01; 需要注意的是安装npcap这个软件 0x02修改配置文件 软件安装目录&#xff1a;C:/Snort/ 配置文…

视频添加字幕

1、依靠ffmpeg 命令 package zimu;import java.io.IOException;public class TestSrt {public static void main(String[] args) {String videoFile "/test/test1.mp4";String subtitleFile "/test/test1.SRT";String outputFile "/test/testout13…

dy六神参数记录分析(立秋篇)

version&#xff1a; 23.9 X-SSSTUB: 搜索&#xff1a;x-tt-dt var hashMap Java.use("java.util.HashMap");hashMap.put.implementation function (a, b) {console.log("hashMap.put: ", a, b);return this.put(a, b);}https://codeooo.blog.csdn.n…