最新要闻
- 头条:4月26日基金净值:嘉实农业产业股票A最新净值1.8912,涨0.34%
- 世界新资讯:华为真的很懂女人:前置拍照 媲美后置
- 神秘技艺挑战“精准刀法”!RTX3070性能大增的原因是什么?
- 电车充电的速度 就要赶上油车加油了_每日快看
- 感受下真正全国产的服务器、PC!能硬 也能软
- 【天天报资讯】Intel显卡多了一个大品牌!41年历史的旌宇
- 全球快资讯:人气流失!跟队:切尔西主场出现空座,这情况太罕见了
- 今日热议:“不动产统一登记”引发房产税热议,上海试点12年效果如何?
- 燃气灶自动熄火原因和处理方法图解(燃气灶自动熄火原因和处理方法)
- 沃尔沃最安全纯电动EX90发布!“我们会的新势力10年都学不会”
- 环球滚动:大熊猫丫丫已启程回国:专机飞往上海 明天抵达浦东机场
- 陈凯歌与倪萍结过婚吗 陈凯歌与倪萍有子女吗
- 微软回应英国CMA阻止收购:不会放弃交易 并将提出上诉
- 厂商私自发布RTX 3060 Ti SUPER!NVIDIA怒了:强制下架
- 世界最新:HarmonyOS 3适配进度:华为P20/Mate 10等13款设备喜提正式版
- 华为Wi-Fi 6技术秀肌肉:350公里时速下网速980Mbps 资讯
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
焦点!浅谈errgroup的使用以及源码分析
本文讲解的是
golang.org/x/sync
这个包中的errgroup
1、errgroup 的基础介绍
学习过 Go 的朋友都知道 Go 实现并发编程是比较容易的事情,只需要使用go
关键字就可以开启一个 goroutine。那对于并发场景中,如何实现goroutine
的协调控制呢?常见的一种方式是使用sync.WaitGroup
来进行协调控制。
使用过sync.WaitGroup
的朋友知道,sync.WaitGroup
虽然可以实现协调控制,但是不能传递错误,那该如何解决呢?聪明的你可能马上想到使用 chan 或者是 context
来传递错误,确实是可以的。那接下来,我们一起看看官方是怎么实现上面的需求的呢?
(相关资料图)
1.1 errgroup的安装
安装命令:
go get golang.org/x/sync//下面的案例是基于v0.1.0 演示的go get golang.org/x/sync@v0.1.0
1.2 errgroup的基础例子
这里我们需要请求3个url来获取数据,假设请求url2时报错,url3耗时比较久,需要等一秒。
package mainimport ("errors""fmt""golang.org/x/sync/errgroup""strings""time")func main() {queryUrls := map[string]string{"url1": "http://localhost/url1","url2": "http://localhost/url2","url3": "http://localhost/url3",}var eg errgroup.Groupvar results []stringfor _, url := range queryUrls {url := urleg.Go(func() error {result, err := query(url)if err != nil {return err}results = append(results, fmt.Sprintf("url:%s -- ret: %v", url, result))return nil})} // group 的wait方法,等待上面的 eg.Go 的协程执行完成,并且可以接受错误err := eg.Wait()if err != nil {fmt.Println("eg.Wait error:", err)return}for k, v := range results {fmt.Printf("%v ---> %v\n", k, v)}}func query(url string) (ret string, err error) {// 假设这里是发送请求,获取数据if strings.Contains(url, "url2") {// 假设请求 url2 时出现错误fmt.Printf("请求 %s 中....\n", url)return "", errors.New("请求超时")} else if strings.Contains(url, "url3") {// 假设 请求 url3 需要1秒time.Sleep(time.Second*1)}fmt.Printf("请求 %s 中....\n", url)return "success", nil}
执行结果:
请求 http://localhost/url2 中....请求 http://localhost/url1 中....请求 http://localhost/url3 中....eg.Wait error: 请求超时
果然,当其中一个goroutine
出现错误时,会把goroutine
中的错误传递出来。
我们自己运行一下上面的代码就会发现这样一个问题,请求 url2 出错了,但是依旧在请求 url3 。因为我们需要聚合 url1、url2、url3 的结果,所以当其中一个出现问题时,我们是可以做一个优化的,就是当其中一个出现错误时,取消还在执行的任务,直接返回结果,不用等待任务执行结果。
那应该如何做呢?
这里假设 url1 执行1秒,url2 执行报错,url3执行3秒。所以当url2报错后,就不用等url3执行结束就可以返回了。
package mainimport ("context""errors""fmt""golang.org/x/sync/errgroup""strings""time")func main() {queryUrls := map[string]string{"url1": "http://localhost/url1","url2": "http://localhost/url2","url3": "http://localhost/url3",}var results []stringctx, cancel := context.WithCancel(context.Background())eg, errCtx := errgroup.WithContext(ctx)for _, url := range queryUrls {url := urleg.Go(func() error {result, err := query(errCtx, url)if err != nil { //其实这里不用手动取消,看完源码就知道为啥了cancel()return err}results = append(results, fmt.Sprintf("url:%s -- ret: %v", url, result))return nil})}err := eg.Wait()if err != nil {fmt.Println("eg.Wait error:", err)return}for k, v := range results {fmt.Printf("%v ---> %v\n", k, v)}}func query(errCtx context.Context, url string) (ret string, err error) {fmt.Printf("请求 %s 开始....\n", url)// 假设这里是发送请求,获取数据if strings.Contains(url, "url2") {// 假设请求 url2 时出现错误time.Sleep(time.Second*2)return "", errors.New("请求出错")} else if strings.Contains(url, "url3") {// 假设 请求 url3 需要1秒select {case <- errCtx.Done():ret, err = "", errors.New("请求3被取消")returncase <- time.After(time.Second*3):fmt.Printf("请求 %s 结束....\n", url)return "success3", nil}} else {select {case <- errCtx.Done():ret, err = "", errors.New("请求1被取消")returncase <- time.After(time.Second):fmt.Printf("请求 %s 结束....\n", url)return "success1", nil}}}
执行结果:
请求 http://localhost/url2 开始....请求 http://localhost/url3 开始....请求 http://localhost/url1 开始....请求 http://localhost/url1 结束....eg.Wait error: 请求出错
2、errgroup源码分析
看了上面的例子,我们对errgroup
有了一定了解,接下来,我们一起看看errgroup
做了那些封装。
2.1 errgroup.Group
errgroup.Group
源码如下:
// A Group is a collection of goroutines working on subtasks that are part of// the same overall task.//// A zero Group is valid, has no limit on the number of active goroutines,// and does not cancel on error.type Group struct { // context 的 cancel 方法cancel func()wg sync.WaitGroup //传递信号的通道,这里主要是用于控制并发创建 goroutine 的数量 //通过 SetLimit 设置过后,同时创建的goroutine 最大数量为nsem chan token // 保证只接受一次错误errOnce sync.Once // 最先返回的错误err error}
看结构体中的内容,发现比原生的sync.WaitGroup
多了下面的内容:
cancel func()
sem chan token
errOnce sync.Once
err error
2.2 WithContext 方法
// WithContext returns a new Group and an associated Context derived from ctx.//// The derived Context is canceled the first time a function passed to Go// returns a non-nil error or the first time Wait returns, whichever occurs// first.func WithContext(ctx context.Context) (*Group, context.Context) {ctx, cancel := context.WithCancel(ctx)return &Group{cancel: cancel}, ctx}
方法逻辑还是比较简单的,主要做了两件事:
- 使用
context
的WithCancel()
方法创建一个可取消的Context
- 将
context.WithCancel(ctx)
创建的cancel
赋值给 Group中的cancel
2.3 Go
1.2 最后一个例子说,不用手动去执行 cancel 的原因就在这里。
g.cancel() //这里就是为啥不用手动执行 cancel的原因
// Go calls the given function in a new goroutine.// It blocks until the new goroutine can be added without the number of// active goroutines in the group exceeding the configured limit.//// The first call to return a non-nil error cancels the group"s context, if the// group was created by calling WithContext. The error will be returned by Wait.func (g *Group) Go(f func() error) {if g.sem != nil { //往 sem 通道中发送空结构体,控制并发创建 goroutine 的数量g.sem <- token{}}g.wg.Add(1)go func() { // done()函数的逻辑就是当 f 执行完后,从 sem 取一条数据,并且 g.wg.Done()defer g.done()if err := f(); err != nil {g.errOnce.Do(func() { // 这里就是确保 g.err 只被赋值一次g.err = errif g.cancel != nil {g.cancel() //这里就是为啥不用手动执行 cancel的原因}})}}()}
2.4 TryGo
看注释,知道此函数的逻辑是:当正在执行的goroutine数量小于通过SetLimit()
设置的数量时,可以启动成功,返回 true,否则启动失败,返回false。
// TryGo calls the given function in a new goroutine only if the number of// active goroutines in the group is currently below the configured limit.//// The return value reports whether the goroutine was started.func (g *Group) TryGo(f func() error) bool {if g.sem != nil {select {case g.sem <- token{}: // 当g.sem的缓冲区满了过后,就会执行default,也代表着未启动成功// Note: this allows barging iff channels in general allow barging.default:return false}} //----主要看上面的逻辑,下面的逻辑和Go中的一样-------g.wg.Add(1)go func() {defer g.done()if err := f(); err != nil {g.errOnce.Do(func() {g.err = errif g.cancel != nil {g.cancel()}})}}()return true}
2.5 Wait
代码逻辑很简单,这里主要注意这里:
//我看这里的时候,有点疑惑,为啥这里会去调用 cancel()方法呢?//这里是为了代码的健壮性,用 context.WithCancel() 创建得到的 cancel,在代码执行完毕之前取消是一个好习惯g.cancel()
// Wait blocks until all function calls from the Go method have returned, then// returns the first non-nil error (if any) from them.func (g *Group) Wait() error { g.wg.Wait() //通过 g.wg.Wait() 阻塞等待所有的 goroutine 执行完if g.cancel != nil { //我看这里的时候,有点疑惑,为啥这里会去调用 cancel()方法呢? //这里是为了代码的健壮性,用 context.WithCancel() 创建得到的 cancel,在代码执行完毕之前取消是一个好习惯 g.cancel()}return g.err}
2.6 SetLimit
看代码的注释,我们知道:SetLimit
的逻辑主要是限制同时执行的 goroutines 的数量为n,当n小于0时,没有限制。如果有运行的 goroutine,调用此方法会报错。
// SetLimit limits the number of active goroutines in this group to at most n.// A negative value indicates no limit.//// Any subsequent call to the Go method will block until it can add an active// goroutine without exceeding the configured limit.//// The limit must not be modified while any goroutines in the group are active.func (g *Group) SetLimit(n int) {if n < 0 {g.sem = nilreturn}if len(g.sem) != 0 {panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", len(g.sem)))}g.sem = make(chan token, n)}
3、errgroup 容易忽视的坑
这个坑是看别人的记录看到的,对errgroup
不太熟悉时,是不小心确实容易掉进去,所以摘抄了过来,如果侵权,请联系删除,谢谢!
原文链接:并发编程包之 errgroup
需求:
开启多个
Goroutine
去缓存中设置数据,同时开启一个Goroutine
去异步写日志,很快我的代码就写出来了:
package mainimport ("context""errors""fmt""golang.org/x/sync/errgroup""time")func main() {g, ctx := errgroup.WithContext(context.Background())// 单独开一个协程去做其他的事情,不参与waitGroupgo WriteChangeLog(ctx)for i:=0 ; i< 3; i++{g.Go(func() error {return errors.New("访问redis失败\n")})}if err := g.Wait();err != nil{fmt.Printf("appear error and err is %s",err.Error())}time.Sleep(1 * time.Second)}func WriteChangeLog(ctx context.Context) error {select {case <- ctx.Done():return nilcase <- time.After(time.Millisecond * 50):fmt.Println("write changelog")}return nil}
结果:
appear error and err is 访问redis失败
代码看着没有问题,但是日志一直没有写入。这是为什么呢?
其实原因就是因为这个ctx
是errgroup.WithContext
方法返回的一个带取消的ctx
,我们把这个ctx
当作父context
传入WriteChangeLog
方法中了,如果errGroup
取消了,也会导致上下文的context
都取消了,所以WriteChangelog
方法就一直执行不到。
这个点是我们在日常开发中想不到的,所以需要注意一下~。
解决方法:
解决方法就是在 go WriteChangeLog(context.Background()) 传入新的ctx
参考资料:
八. Go并发编程--errGroup
并发编程包之 errgroup
上面这个案例中讲了一个容易忽视的坑,大家可以看看
关键词:
-
焦点短讯!Blazor UI库 Bootstrap Blazor 快速上手 (v7.5.7)
最近组件库更新比较频繁,有些同学感觉有点迷茫,就着今天刚上了张老板一节课立马撸个新的上手教程回馈社区,;
来源: -
头条:4月26日基金净值:嘉实农业产业股票A最新净值1.8912,涨0.34%
4月26日,嘉实农业产业股票A最新单位净值为1 8912元,累计净值为1 8912元,较前一交易日上涨0 34%。历史数
来源: 焦点!浅谈errgroup的使用以及源码分析
焦点短讯!Blazor UI库 Bootstrap Blazor 快速上手 (v7.5.7)
关注:使用youtube-dl和yt-dlp下载视频!
头条:4月26日基金净值:嘉实农业产业股票A最新净值1.8912,涨0.34%
世界新资讯:华为真的很懂女人:前置拍照 媲美后置
神秘技艺挑战“精准刀法”!RTX3070性能大增的原因是什么?
电车充电的速度 就要赶上油车加油了_每日快看
感受下真正全国产的服务器、PC!能硬 也能软
【天天报资讯】Intel显卡多了一个大品牌!41年历史的旌宇
全球快资讯:人气流失!跟队:切尔西主场出现空座,这情况太罕见了
今日热议:“不动产统一登记”引发房产税热议,上海试点12年效果如何?
燃气灶自动熄火原因和处理方法图解(燃气灶自动熄火原因和处理方法)
09 管理内存对象|焦点讯息
沃尔沃最安全纯电动EX90发布!“我们会的新势力10年都学不会”
环球滚动:大熊猫丫丫已启程回国:专机飞往上海 明天抵达浦东机场
陈凯歌与倪萍结过婚吗 陈凯歌与倪萍有子女吗
关于在linux中使用tcpdump命令进行简单的抓包操作
Geotools处理shape文件 世界微速讯
中小型项目统一处理请求重复提交 天天热消息
交易商协会:一季度绿色债务融资工具共发行429.88亿元
微软回应英国CMA阻止收购:不会放弃交易 并将提出上诉
厂商私自发布RTX 3060 Ti SUPER!NVIDIA怒了:强制下架
世界最新:HarmonyOS 3适配进度:华为P20/Mate 10等13款设备喜提正式版
华为Wi-Fi 6技术秀肌肉:350公里时速下网速980Mbps 资讯
【速看料】国行索尼PS5第一次降价!数字版只需2729元 货源充足
环球今亮点!进一步规范我市农资经营市场秩序
如何在jmeter中把响应中的数据提取出来并引用 天天头条
最无益一日曝十日寒的上一句_最无益一日曝十日寒-当前快看
【财经分析】公募REITs一季度业绩喜忧参半 短期波动无碍机构长期看多
EVGA一怒之下决裂不做N卡!灵魂人物却投奔NVIDIA 焦点日报
每日头条!动视暴雪CEO:收购案还未盖棺定论 优势在我
DXO拍照得分全球第一 Find X6 Pro重磅更新:相机新功能诚意满满
大发地产已物色上会栢诚为新核数师 2022年报将在8月31日前刊发_天天时快讯
天天滚动:elastic-job源码(2)-选举机制
环球热头条丨第五期(2022-2023)传统行业云原生技术落地调研报告——金融篇
日债市场等待交投指引 中长端收益率明显回落
人民银行上海总部:一季度长三角地区人民币贷款增加3.71万亿元 同比多增8712亿元
钱包给你!关羽五五新皮肤百相守梦即将登场 太帅了
抱抱脸开源GPT不用注册直接玩!网友:“安卓时代”来了 环球观点
全新Win10精简版OS发布下载!CPU/内存占用暴降:游戏性能起飞
国内已超越GPS 定位精度优于1米 我国已有45颗北斗卫星在轨:还要再发1-3颗备份_全球快看点
世界新资讯:无限逼近现实世界光影特效!《赛博朋克2077》全景光追体验:开启DLSS 3帧率提升超过300%
快船两个时代苦涩轮回:百亿老板无奈乔治仍乐观 卡椒明年最后一搏 天天看点
重磅!阿里云云原生合作伙伴计划全新升级:加码核心权益,与伙伴共赢新未来_当前滚动
专家建议WestConnex对内蒙古房地产价格产生长期积极影响
【独家焦点】合肥一景区举办发呆大赛奖金3000元:睡着也算、但有条件
追缴并罚款545.8万元!又一网络主播偷逃税款被查
当前速讯:武汉风筝节遇上大风天!巨型风筝把人“卷”上天
世界焦点!华为鸿蒙OS 3最新公测来了:支持荣耀15款手机、6年前的机型也有份
环球播报:锐龙7000X3D处理器连烧数起 主板厂商集体行动:AMD回应
西安地铁4月28日将迎来五一客流高峰 部分线路延长运营时间_全球热议
天天关注:【MAUI Blazor踩坑日记】3.Windows标题栏自定义颜色,运行时改变颜色
ThingsBoard 前端项目内置部件开发
记录-Vue移动端日历设计与实现
今日热门!iOS描述文件(.mobileprovision)一键申请
vue-router3.x和vue-router4.x相互影响的问题记录
许婷律师联系方式_许婷
解锁五一新玩法——第三站:黑龙江·森林氧吧
内地5月26日上映!《小美人鱼》新预告:女主被鲨鱼追杀-天天快资讯
给AI小姐姐留着 西数16TB硬盘到手1549元(非SMR)
5代都玩腻了 5月17日或公布《GTA6》新消息 期待吗? 当前热文
环球观热点:彻底放弃Win7!U盘软件Rufus发布4.0版本
视效大片!《变形金刚7:超能勇士崛起》确认引进内地:预计6月上映
四大会计师事务所排名百科(四大会计师事务所排名)
Java中关于String类以及字符串拼接的问题 世界新动态
低代码是开发的未来,还是只能解决边角问题的鸡肋? 全球热点评
环球播报:有奖征文丨【玩转Cloud Studio】第二季来啦!
ReactNative 桥接原生原子组件(一) 今日热闻
Java程序部署成Windows服务-全球独家
世界即时:台湾拚非核疯绿电 企业买高中培养风电人才
天天观速讯丨【财经分析 】钢厂纷纷减产,钢价能否止跌?
女子淄博买切糕2种口味称重仅6元火了!五一客流量爆满:喊话推荐其他山东城市
中国广电合约机明天上市:iPhone 14最低2586元 全面支持5G|最资讯
并非永久关停!天涯社区官方:会回来的 天天百事通
环球滚动:一加功不可没!OPPO成为中国手机市场Q1安卓销量第一名
速看:杭州、合肥土拍又“火爆”了
患者反复腹痛7年!南华医院一招肠菌移植显奇效-视讯
读书笔记丨远程服务调用和RESTful,如何分析和抉择?
国内公路编码规则
收评:创业板指收涨1.54% 新能源赛道大幅反弹
湖南工学院与衡阳师范学院联合举办2023年“一校一书”阅读推广暨读书节活动开幕式 焦点消息
售价接近110万 网友绘制比亚迪仰望U8改装版:高低也得整一辆|天天热消息
微头条丨安卓之光!小米连续做了4代Ultra:米粉评价“方向对了”
要闻:研究表明咖啡+茶效果更佳:可降低死亡风险 但要适量
热点聚焦:买硬座票跑卧铺睡男子引发热议!他还教育乘务员:提高服务意识
主摄硬件无升级!三星Galaxy S24 Ultra将依靠软件优化拍摄质量
楚天科技:一次性耗材领域已完成质量体系认证,目前已有销售订单
MySQL 安装及配置 当前快看
四级英语冲刺高频500词
在毫秒量级上做到“更快”!DataTester 助力飞书提升页面秒开率
[译]在C#中使用IComparable和IComparer接口 每日短讯
(二)asyncio的简单使用,python异步高效处理数据,asyncio.get_event_loop(),loop.run_until_complete(
全球看点:4月26日西南地区乙醇市场行情震荡整理
大范围降雨将重启 暴雨袭击6省:覆盖五一假期 环球即时看
特斯拉Autopilot每917万公里才一起事故:比人驾安全性高773% 世界新动态
旅美大熊猫“丫丫”将于当地4月26日归国:身体健康状态相对平稳
三星Exynos 2400回归!性能爆发 剑指高通骁龙8 Gen3-世界快讯
世界视点!撕掉油腻 三重功效:凯迪克男士洗面奶280g装19.9元
天天观天下!剪窗花的来历和故事_剪窗花的来历
市场监管总局:对明显违法违规的主播和商家要依法严厉处罚-全球最资讯