最新要闻
- 网络销售渠道有哪些(网络销售渠道)
- 打卡黎里新去处!德心堂造物空间免费向公众开放
- 河南发布暴雨黄色预警!本轮降水何时结束?
- 这么夸张?整部剧的人,一个比一个能装
- 东土科技(300353.SZ):上半年净亏损1.1亿元
- 恒大地产集团:所有恒大地产存续公司债券将继续停牌
- 制造名城绿意浓——株洲荣获“2023中国最具生态竞争力城市”走笔
- 山市原文断句(山市全文翻译)
- 车主赞叹:极氪式安全金身不破,电池包火烧都烧不着
- 艾凯生物完成亿元A++轮融资
- 现货黄金短线震荡修复
- 培育创新思维 普及知产文化——首届长三角地区大学生知识产权知识竞赛夏令营活动让课堂“行走”起来
- 【明日天气预报】武汉2023年08月28日天气预报,阴转多云,北风3-4级转<3级
- 首款氮化镓充电桩发布:7KW功率 四种充电方式
- iPhone 15系列配色全揭晓:标准版5款、Pro版4款
- 功能过于臃肿!Edge浏览器终于迎来“瘦身”
手机
奥本海默要来了,我还在怀念芭比粉
比亚迪股东户数下降21.06%,户均持股90.76万元
- 奥本海默要来了,我还在怀念芭比粉
- 比亚迪股东户数下降21.06%,户均持股90.76万元
- 甘肃武威市民勤县发生3.0级地震
- 微信表情包公众号朋友圈是啥(微信表情包公众号)
- 第九批国家组织药品集采相关药品信息填报工作启动
- 快乐开学季|永安街小学秋季开学典礼满“新”欢喜
家电
Go 上下文的理解与使用
为什么需要 context
在 Go 程序中,特别是并发情况下,由于超时、取消等而引发的异常操作,往往需要及时的释放相应资源,正确的关闭 goroutine。防止协程不退出而导致内存泄露。如果没有 context,用来控制协程退出将会非常麻烦,我们来举一个例子。
假如说现在一个协程A开启了一个子协程B,这个子协程B又开启了另外两个子协程B1和B2来运行不同的任务,协程B2又开启了协程C来运行其他任务,现在协程A通知子协程B该退出了,这个时候我们需要完成这样的操作:A通知B退出,B退出时通知B1、B2退出,B2退出时通知C退出:
【资料图】
func TestChanCloseGoroutine(t *testing.T) {fmt.Printf("开始了,有%d个协程\n", runtime.NumGoroutine())var (chB = make(chan struct{})chB1 = make(chan struct{})chB2 = make(chan struct{})chC = make(chan struct{}))// 协程Ago func() {// 协程Bgo func() {// 协程B1go func() {for {select {case <-chB1:returndefault:}}}()// 协程B2go func() {// 协程Cgo func() {for {select {case <-chC:returndefault:}}}()for {select {case <-chB2:// 通知协程C退出chC <- struct{}{}returndefault:}}}()for {select {case <-chB:chB1 <- struct{}{}chB2 <- struct{}{}returndefault:}}}()// 1秒后通知B退出time.Sleep(1 * time.Second)chB <- struct{}{}// A后续没有任务了,会自动退出}()time.Sleep(2 * time.Second)fmt.Printf("最终结束,有%d个协程\n", runtime.NumGoroutine())}// 结果开始了,有2个协程最终结束,有2个协程// tips: Go Test 会启动两个额外的 goroutine 来运行代码,所以初始就会有2个 goroutine
通过 channel 来控制各个 goroutine 的关闭,程序看上去一点也不优雅。而且这才仅仅四个 goroutine ,就已经显得有些力不从心了,在真实的业务中,哪怕一个简单的 http 请求,都不可能启用四个 goroutine 就能够完成,且子协程的层级也绝非只有寥寥的三层!
context 是什么
context 在 Go 中是一个接口,它的定义如下:
type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key any) any}
- Deadline 用来获取 ctx 的截止时间,如果没有截至时间,ok 将返回 false;
- Done 里面是一个通道,当 ctx 被取消时,会返回一个关闭的 channel,如果该 ctx 永远都不会被关闭,则返回 nil;
- Err 返回的 ctx 取消的原因,如果 ctx 没有被取消,会返回 nil。如果已经关闭了,会返回被关闭的原因,如果是被取消的会返回 canceled,超时的显示 deadline exceeded;
- Value 会返回 ctx 中储存的值,会从当前 ctx 中一路向上追溯,如果整条 ctx 链中都没有找到值,则会返回nil。
context 的基本结构比较简单,里面也只有四个方法,如果到此没有理解四个方法也没有关系,下文会使用到这四个方法,届时将会很自然的掌握它们。
context 接口的实现
context 有四个不同的实现:emptyCtx、cancelCtx、timerCtx、valueCtx:
type emptyCtx intfunc (*emptyCtx) Deadline() (deadline time.Time, ok bool) {return}func (*emptyCtx) Done() <-chan struct{} {return nil}func (*emptyCtx) Err() error {return nil}func (*emptyCtx) Value(key any) any {return nil}
emptyCtx 是一个实现了 context 接口的整型,它不能储存信息,也不能被取消,它被当作根节点 ctx。cancelCtx、timerCtx、valueCtx 由于篇幅原因,这里不放出它们的源码,只解释它们的作用:cancelCtx 是一个可以主动取消的 ctx。timerCtx 也是一个可以主动取消的 ctx,不同于 cancelCtx,它还储存着额外的时间信息,当时间条件满足后,会自动取消该 ctx,利用这点,可以实现超时机制。valueCtx 比较简单,用来创建一个携带键值的 ctx。
context 的基本使用
创建一个根节点
创建根节点有两种方法:
ctx := context.Background()ctx := context.TODO()
这两种方法其实本质上都是初始化了一个 emptyCtx:
var (background = new(emptyCtx)todo = new(emptyCtx))func Background() Context {return background}func TODO() Context {return todo}
可以看到,在代码中,这两个函数其实是一模一样的,只是用于不同场景下:Background 推荐在主函数、初始化和测试中使用,TODO 用于不清楚使用哪个 context 时使用。根节点 ctx 不具备任何意义,也不能被取消。
创建一个子 ctx
可以通过WithCancel、WithDeadline、WithTimeout、WithValue 这四个主要的函数来创建子 ctx ,创建一个子 ctx 必须指定其归属的父 ctx,由此来形成一个上下文链,用来同步 goroutine 信号。来看一下它们的简单使用:
WithCancel
用来创建一个 cancelCtx,它可以被主动取消 :
func TestCtxWithCancel(t *testing.T) {ctx := context.Background()ctx, cancel := context.WithCancel(ctx)go func() {for {select {// 还记得前文提到的Done的方法吗// 当 ctx 取消时,ctx.Done()对应的通道就会关闭,case也就会被执行case <-ctx.Done():// ctx.Err() 会获取到关闭原因哦fmt.Println("协程关闭", ctx.Err())returndefault:fmt.Println("继续运行")time.Sleep(100 * time.Millisecond)}}}()// 等待一秒后关闭time.Sleep(1 * time.Second)cancel()// 等待一秒,让子协程有时间打印出协程关闭的原因time.Sleep(1 * time.Second)}// 结果继续运行继续运行……协程关闭 context canceled
WithDeadline
用来创建一个 timerCtx,当时间条件满足后,它会被自动取消 :
func TestCtxWithDeadline(t *testing.T) {ctx := context.Background()// 等待2秒后自动关闭ctx, cancel := context.WithDeadline(ctx, time.Now().Add(2*time.Second))defer cancel()// Deadline 前文也提到了,还记得吗?用来获取当前任务的截至时间if t, ok := ctx.Deadline(); ok {// time.DateTime 是 go1.20 版本的一个常量,其值是:"2006-01-02 15:04:05"fmt.Println(t.Format(time.DateTime))}go func() {select {case <-ctx.Done():// 手动关闭 context canceled// 自动关闭 context deadline exceededfmt.Println("协程关闭", ctx.Err())return}}()time.Sleep(3 * time.Second)}// 结果2023-05-10 18:00:36协程关闭 context deadline exceeded// 将最后的等待时间更改为一秒func TestCtxWithDeadline(t *testing.T) {……time.Sleep(1 * time.Second)}// 结果2023-05-10 18:01:45协程关闭 context canceled
哪怕 WithDeadline 到达指定时间会自动关闭,但依然推荐使用 defer cancel() 。这是因为如果任务已经完成了,但是自动取消仍需要1天时间,那么系统就会白白浪费资源在这1天上。
WithTimeout
与 WithDeadline
同理,只不过是 WithTimeout 用来接受一个过期时间,而不是接受一个过期时间节点:
func TestCtxWithTimeout(t *testing.T) {ctx := context.Background()ctx, cancel := context.WithTimeout(ctx, 2*time.Second)defer cancel()go func() {select {case <-ctx.Done():fmt.Println("协程关闭", ctx.Err())return}}()time.Sleep(3 * time.Second)}// 结果协程关闭 context deadline exceeded
WithValue
用来创建一个 valueCtx:
// 向上找到最近的上下文值func TestCtxWithValue(t *testing.T) {ctx := context.Background()ctx1 := context.WithValue(ctx, "key", "ok")ctx2, _ := context.WithCancel(ctx1)// Value 会一直向上追溯到根节点,获取当前上下文携带的值,value := ctx2.Value("key")if value != nil {fmt.Println(value)}}// 结果ok
这四个函数都是创建一个新的子节点,并不是直接修改当前 ctx,所以最后生成的 ctx 链有可能是这样的:
使用 ctx 退出 goroutine
回到开头提到的那个例子,我们使用 context 对其改造一下:
func TestCtxCloseGoroutine(t *testing.T) {fmt.Printf("开始了,有%d个协程\n", runtime.NumGoroutine())ctx := context.Background()// 协程Ago func(ctx context.Context) {ctx, cancel := context.WithCancel(ctx)// 协程Bgo func(ctx context.Context) {// 协程B1go func(ctx context.Context) {for {select {case <-ctx.Done():returndefault:}}}(ctx)// 协程B2go func(ctx context.Context) {// 协程Cgo func(ctx context.Context) {for {select {case <-ctx.Done():returndefault:}}}(ctx)for {select {case <-ctx.Done():returndefault:}}}(ctx)for {select {case <-ctx.Done():returndefault:}}}(ctx)// 1秒后通知退出time.Sleep(1 * time.Second)cancel()// A后续没有任务了,会自动退出}(ctx)time.Sleep(2 * time.Second)fmt.Printf("最终结束,有%d个协程\n", runtime.NumGoroutine())}// 结果开始了,有2个协程最终结束,有2个协程
可以看到,和使用 channel 控制 goroutine 退出相比,context 大大降低了心智负担。context 优雅的实现了某一层任务退出,下层所有任务退出,上层任务和同层任务不受影响。
Go 语言最佳实践:每次 context 的传递都应该直接使用值传递,不应该使用指针传递。这样可以防止上下文的值被多个并发的 goroutine 修改而导致竞争问题。虽然使用值传递会导致一些微小的性能开销,因为每次传递上下文时都需要复制一份数据,但它提供了更好的并发安全性和程序可靠性。另外,由于上下文采用了值传递,也不应该向上下文中存入较大的数据,从而导致性能问题。
关键词:
Go 上下文的理解与使用
动力调整,14.59万起售,详解2022款深蓝SL03增程版
奥本海默要来了,我还在怀念芭比粉
第22次TRIX金叉,九芝堂买入胜率如何?看数据说n
菏泽市百花生物科技有限公司(关于菏泽市百花生物科技有限公司简述)
比亚迪股东户数下降21.06%,户均持股90.76万元
造字程序如何使用(造字程序)
萨姆·亨弗里斯(关于萨姆·亨弗里斯简述)
金活医药:2023年中期营收同比增长31.8%
关于印发《2023年浙江省成人高校招生工作实施方案》的通知
网络销售渠道有哪些(网络销售渠道)
优宁维:8月25日融资买入142.86万元,融资融券余额5015.77万元
家里进蛇有什么预兆 男子上厕所关门夹住蛇求助妻子 基本情况讲解
美农生物:8月25日融资净买入458.82万元,连续3日累计净买入824.25万元
花鲢鱼刺多吗 花鲢鱼
聚赛龙:8月25日融资买入44.63万元,融资融券余额3130.99万元
宁波市人民政府关于加大扶贫力度若干政策意见的通知(关于宁波市人民政府关于加大扶贫力度若干政策意见的通知简述)
打卡黎里新去处!德心堂造物空间免费向公众开放
脚本是什么软件 脚本是什么
老旧小区改造见成效
虚拟电厂帮电网“减负”
初始化dsound失败是什么意思(dsound dll)
河南发布暴雨黄色预警!本轮降水何时结束?
中国石油长庆油田天然气累产突破6000亿立方米
菲律宾八打雁省附近海域一渔船沉没 可能发生燃油泄漏
无锡银行上半年净利润 12.31 亿元 同比增长 20.68%
这么夸张?整部剧的人,一个比一个能装
东土科技(300353.SZ):上半年净亏损1.1亿元
一个不眠的非遗夜!长春市图书馆“乐享阅会”非遗体验大集欢乐多
甘肃武威市民勤县发生3.0级地震
新奥“泛能微网”项目遍地开花 助力工业园区绿色低碳发展
赵睿成球员代表被采访:展望对阵南苏丹 不觉得琼斯比顶级外援强
保护本土种质资源 四川采取了这三招
佛教霸气的四字禅语 佛教霸气的四字禅语
意向购销成交金额558.8亿元!第五届中国粮食交易大会成果丰硕
夏威夷电力实业(HE.N)上涨50%触发停牌
辽宁锦州可提供海信洗衣机维修服务地址在哪
马绍尔群岛饱受美国核试验之苦,当地居民:不相信“核污染物排海是安全的”
橙天嘉禾(01132.HK):上半年股权持有人应占亏损2440万港元
恒大地产集团:所有恒大地产存续公司债券将继续停牌
吉林高速力推“路旅”融合:服务区里寻味电影文化
爱康科技:上半年净利润同比翻番 异质结领军企业曙光已现
主营产品收入及毛利率提高 飞龙股份上半年净利同比增长491.22%
水质专员康静伟和王嘉希:只为这一泓清水永续北上
V观财报|中国恒大上半年净亏392.5亿元
李晟跟李佳航是夫妻吗 好友公开发文抱不平)
核磁共振是什么样子(核磁共振是什么)
两室两厅改成三室一厅(两室两厅改三室一厅)
梦幻西游五门派组合有哪些(梦幻西游五开门派组合)
微信表情包公众号朋友圈是啥(微信表情包公众号)
鸿通管廊投资:2023年中报净利润发生亏损
制造名城绿意浓——株洲荣获“2023中国最具生态竞争力城市”走笔
山市原文断句(山市全文翻译)
女朋友不爱你的前兆(女友不爱你的35种表现)
荷兰企业期待共享共赢 看好中国服务贸易市场
车主赞叹:极氪式安全金身不破,电池包火烧都烧不着
山西省第十届“好记者讲好故事”演讲比赛举行
中国能出如此破天荒的人才政策, 美国还等什么? 美智库一语中的|文化纵横
小米平板6首次降价:仅1799元起!
【地评线】京彩好评:文化消费 正在成为一种刚需
艾凯生物完成亿元A++轮融资
第九批国家组织药品集采相关药品信息填报工作启动
现货黄金短线震荡修复
这5年,帮您省事
我国将立法促进学前教育普及普惠安全优质发展
快乐开学季|永安街小学秋季开学典礼满“新”欢喜
节目效果拉满!马斯克在扎克伯格家门口测试自动驾驶 戏言上门决斗
培育创新思维 普及知产文化——首届长三角地区大学生知识产权知识竞赛夏令营活动让课堂“行走”起来
@云南人,这份文件与每个人的健康息息相关
华西证券给予英华特增持评级 业绩延续高增 商空应用表现亮眼
两部门会商部署重点地区防汛防台风工作
九元航空托运行李一般多少钱一公斤 九元航空每超行李一公斤多少钱
坐飞机行李箱尺寸规定 坐飞机行李箱多大尺寸符合要求
莽荒蛇兽(关于莽荒蛇兽简述)
菀坪镇志(关于菀坪镇志简述)
菁华名门(关于菁华名门简述)
萌娘的冒险(关于萌娘的冒险简述)
萌娘大召唤(关于萌娘大召唤简述)
ST鹏博士上半年净利9464.78万元增4成 多地算力中心启动项目规划
海口和三亚新房价格分别迎来“6连涨”和“7连涨”,二手房价格却跌了…
济南天桥桥面发生四车交通事故:伤者已送医,无生命危险
力图控股(01008.HK)中期持续经营业务收益约3.35亿港元 同比减少30.0%
节支降耗有“妙招”,霄云煤矿“绿色能源+避峰填谷”模式见实效
【明日天气预报】武汉2023年08月28日天气预报,阴转多云,北风3-4级转<3级
敦煌消防:微宣讲小分队走街串巷送“安全”
国家能源局:进一步加强电力市场管理委员会规范运作
注意啦!青岛市疾控中心发布最新健康提醒
我国拟修订治安管理处罚法更好维护社会治安秩序
比亚迪方程豹“豹5”车型盲订售价区间30-40万元
百川股份董秘回复:股价影响因素很多,大盘波动、宏观形势、投资者情绪等均会造成股价波动,如有相关计划
中国盐业协会:我国食盐保障充足,不需要囤盐
二次报销需要什么材料
首款氮化镓充电桩发布:7KW功率 四种充电方式
iPhone 15系列配色全揭晓:标准版5款、Pro版4款
功能过于臃肿!Edge浏览器终于迎来“瘦身”
还能好心顺路带熟人吗?男子开车送同学出车祸被判赔19万
政策直达快享 企业轻装前行(深度观察·畅通资金“血脉”,促进民营经济发展壮大)
28.28万元起,起亚高能纯电轿跑EV6正式上市
根据LPL和LCK目前的整体状态,今年S赛的冠军会是谁呢?
暴雨预警:7省区将现大到暴雨 四川重庆等部分地区有大暴雨