上次说了一下Go语言布道师 Dave Cheney对Go并发的建议,个人觉得最重要的一条,这次主要想说一下这个。

8.3. Never start a goroutine without knowning when it will stop(永远不要在不知道何时停止的情况下启动 goroutine)

我们的需求

我这边当时有个需求是这样的,我们有个考试系统的,每次学员答完试卷去检查一下这次交卷是否是这次考试的最后一份试卷,如果是最后一份试卷的话,需要计算这次考试的总成绩,生成考试的学习报告,当然了,如果不是最后一份试卷的话啥也不干。

生成试卷和报告是必须要生成的,不能出现考完试了没有总成绩和总报告。

接到这个需求的时候,我首先想到的是使用golang的goroutine去异步算出成绩生成报告。然后写代码就是这样的。

go createReport()

这不刚好是8.3 永远不要这样写的建议么?

然后觉得应该写一个管理goroutine异步执行任务的类库,创建执行销毁都由这个管理工具去执行。准备写的时候发现B站的代码里有一个这样的类库,异步执行的类库。

B站的类库

B站代码里面异步任务是这个文件

openbilibili-go-common-master/library/sync/pipeline/fanout/fanout.go

var (
// ErrFull chan full.
ErrFull = errors.New("fanout: chan full")
stats = prom.BusinessInfoCount
traceTags = []trace.Tag{
trace.Tag{Key: trace.TagSpanKind, Value: "background"},
trace.Tag{Key: trace.TagComponent, Value: "sync/pipeline/fanout"},
}
) type options struct {
worker int
buffer int
} // Option fanout option
type Option func(*options) // Worker specifies the worker of fanout
func Worker(n int) Option {
if n <= 0 {
panic("fanout: worker should > 0")
}
return func(o *options) {
o.worker = n
}
} // Buffer specifies the buffer of fanout
func Buffer(n int) Option {
if n <= 0 {
panic("fanout: buffer should > 0")
}
return func(o *options) {
o.buffer = n
}
} type item struct {
f func(c context.Context)
ctx context.Context
} // Fanout async consume data from chan.
type Fanout struct {
name string
ch chan item
options *options
waiter sync.WaitGroup ctx context.Context
cancel func()
} // New new a fanout struct.
func New(name string, opts ...Option) *Fanout {
if name == "" {
name = "fanout"
}
o := &options{
worker: 1,
buffer: 1024,
}
for _, op := range opts {
op(o)
}
c := &Fanout{
ch: make(chan item, o.buffer),
name: name,
options: o,
}
c.ctx, c.cancel = context.WithCancel(context.Background())
c.waiter.Add(o.worker)
for i := 0; i < o.worker; i++ {
go c.proc()
}
return c
} func (c *Fanout) proc() {
defer c.waiter.Done()
for {
select {
case t := <-c.ch:
wrapFunc(t.f)(t.ctx)
stats.State(c.name+"_channel", int64(len(c.ch)))
case <-c.ctx.Done():
return
}
}
} func wrapFunc(f func(c context.Context)) (res func(context.Context)) {
res = func(ctx context.Context) {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 64*1024)
buf = buf[:runtime.Stack(buf, false)]
log.Error("panic in fanout proc, err: %s, stack: %s", r, buf)
}
}()
f(ctx)
if tr, ok := trace.FromContext(ctx); ok {
tr.Finish(nil)
}
}
return
} // Do save a callback func.
func (c *Fanout) Do(ctx context.Context, f func(ctx context.Context)) (err error) {
if f == nil || c.ctx.Err() != nil {
return c.ctx.Err()
}
nakeCtx := metadata.WithContext(ctx)
if tr, ok := trace.FromContext(ctx); ok {
tr = tr.Fork("", "Fanout:Do").SetTag(traceTags...)
nakeCtx = trace.NewContext(nakeCtx, tr)
}
select {
case c.ch <- item{f: f, ctx: nakeCtx}:
default:
err = ErrFull
}
stats.State(c.name+"_channel", int64(len(c.ch)))
return
} // Close close fanout
func (c *Fanout) Close() error {
if err := c.ctx.Err(); err != nil {
return err
}
c.cancel()
c.waiter.Wait()
return nil
} 使用方法
ca := New("cache", Worker(100), Buffer(1024))
var run bool
ca.Do(context.Background(), func(c context.Context) {
run = true
})

主要分析一下这个类库,以后自己写或者使用的时候就能得心应手了,而且这个类库也算是创建goroutine,通过channel通信的经典写法吧



1.New方法调用的时候,会创建buffer个ch channel,worker个goroutine.由于ch是空的,worker个goroutine会阻塞住,一直等待有程序往ch里面写入数据

2.Do函数一但被调用,会传入异步任务的func,func就会写入到ch里面了,goroutine就可以从ch里面读取到数据,并且执行这个数据里面的func

践行了这个原则

不要通过共享内存来通信,要通过通信来共享内存

有个需要注意的点,就Do函数在执行代码是这样的



代码里面可以看到在c.ch 写入数据的时候,如果超过c.ch的长度(测试代码里面是1024)就报错返回了,这样就不能保证每个异步任务都能稳定执行了,这样的结果就是,如果程序处理慢或者异步任务数量比较多的话(超过1024),异步任务就无法完成。当然了,我们也可以修改代码改成等待ch的里面数据被goroutine处理的小于1024了,也会执行,这样就变成一个不可控的程序了,如果有3000个异步任务没人知道执行完成需要多长时间,然后我们程序如果重启的话,是等待它完成重启还是强制重启,等待完成不知道需要等待多长时间,强制重启就无法保证任务能够全部完成。

最终方案

为了一定能够在任何异常情况算出分数和生成报告,最后使用消息队列做了这件事,发送完成答卷的消息,接收到完成答卷的消息之后算出分数生成报告。做完之后虽然保证了可靠性,但是觉得自己发消息自己收消息确实也很别扭。

不知道其他童鞋有没有更好的更合理的方案。

golang开发:go并发的建议(完)的更多相关文章

  1. golang开发:go并发的建议

    这个是前段时间看到Go语言的贡献者与布道师 Dave Cheney对Go并发的建议或者叫使用的陷阱(不是我自己的建议),结合自己最近几年对gorotine的使用,再回头看这几条建议,真的会茅塞顿开,觉 ...

  2. 用golang开发系统软件的一些细节

    用golang开发系统软件的一些细节 作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 (本文的pdf版本) ...

  3. window / Linux 下 Golang 开发环境的配置

    一直专注于使用python语言进行程序开发,但是却又一直被它的性能问题所困扰,直到遇到了天生支持高并发的Golang,这似乎也成了学习go语言最理所当然的理由.下面介绍下Go语言开发环境搭建的步骤: ...

  4. Linux下配置Golang开发环境

    前几天无意间看到了微信推送的golang开发的消息,看到golang那么牛逼,突然心血来潮想学习一下go.工欲善其事必先利其器,想做go开发,必须先配置好go的开发环境(就像开发Java先安装配置jd ...

  5. 关于Web开发里并发、同步、异步以及事件驱动编程的相关技术

    一.开篇语 我的上篇文章<关于如何提供Web服务端并发效率的异步编程技术>又成为了博客园里“编辑推荐”的文章,这是对我写博客很大的鼓励,也许是被推荐的原因很多童鞋在这篇文章里发表了评论,有 ...

  6. Golang开发环境搭建-Vim篇

    一.一个干净的环境 找个干净的基础环境,方便确认每个搭建步骤后的效果: Ubuntu 14.04 x86_64 vim version 7.4.52 go version go1.4beta1 lin ...

  7. Golang开发支持平滑升级(优雅重启)的HTTP服务

    Golang开发支持平滑升级(优雅重启)的HTTP服务 - tabalt的博客 http://tabalt.net/blog/graceful-http-server-for-golang/ http ...

  8. Visual Studio Code配置GoLang开发环境

    Visual Studio Code配置GoLang开发环境 在Visual Studio Code配置GoLang开发环境 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页: ...

  9. [golang]Golang实现高并发的调度模型---MPG模式

    Golang实现高并发的调度模型---MPG模式 传统的并发形式:多线程共享内存,这也是Java.C#或者C++等语言中的多线程开发的常规方法,其实golang语言也支持这种传统模式,另外一种是Go语 ...

随机推荐

  1. 微信公众号授权登录后报redirect_uri参数错误的问题

      在进行微信公众号二次开发的时候,需要通过授权码模式来进行微信授权.比如,在进行登录的时候,用户点击了登录按钮,然后弹出一个授权框,用户点击同意后,就可以获取用户的OpenId等信息了.这篇文章主要 ...

  2. Min GW 安装教程(转载)

    下载方式一:1.百度搜索关键词"mingw",点击第一个:2.进入官网后,点击右上角的"Downloads":3.进入第三个页面后,点击"mingw- ...

  3. 开源有魔力 - DolphinScheduler 的 Apache 之路

    关于 Apache DolphinScheduler社区 Apache DolphinScheduler(incubator) 于17年在易观数科立项,19年3月开源, 19 年8月进入Apache ...

  4. React报错之Encountered two children with the same key

    正文从这开始~ 总览 当我们从map()方法返回的两个或两个以上的元素具有相同的key属性时,会产生"Encountered two children with the same key&q ...

  5. JS/java实现QQ空间自动点赞

    使用方法: 1:进入QQ空间 2:复制下面代码 3:按F12或右键审查元素 进入控制台 也就是console 4:粘贴  回车键  喝口水 5:如果嫌慢的话可以 修改这段代码. window.setI ...

  6. 01 - 快速体验 Spring Security 5.7.2 | 权限管理基础

    在前面SpringBoot 2.7.2 的系列文章中,已经创建了几个 computer 相关的接口,这些接口直接通过 Spring Doc 或 POSTMAN 就可以访问.例如: GET http:/ ...

  7. CCC3.0 NFC OWNER PAIRING

    OWNER PAIRING 本篇只介绍所有操作都成功执行的场景,中间如果出现异常,需要翻看规范决定接下来的操作 一些密钥 公私密钥对(Vehicle.PK&Vehicle.SK) Endpoi ...

  8. python_跨文件二维全局变量传参

    因为业务需要,我需要创建一个可以在多模块公用全局变量,根据https://www.jianshu.com/p/6cee728f3490的代码,因为他提供的只能生成 { k: v, kk: v ... ...

  9. 【面试题】js实现将excel表格copy到页面

    js实现将excel表格copy到页面 点击打开视频讲解更加详细 其实最核心的技术,还是copy的是我们粘贴板上的数据 就像平常怎么粘贴复制其他的数据一样,只是我们在excel粘贴的是一个表格数据 这 ...

  10. Currtid 函数与性能问题

    对于Oracle ,一条tuple 的 rowid正常是不会变化的(引发row movement的操作除外,如:跨分区迁移update,表收缩),因此,应用设计上可以方便的使用rowid,加快访问速度 ...