关于golang:一文带你更方便的控制-goroutine

10次阅读

共计 2596 个字符,预计需要花费 7 分钟才能阅读完成。

上一篇咱们讲了 go-zero 中的并发工具包 core/syncx

从整体剖析来看,并发组件次要通过 channel + mutex 控制程序中协程之间沟通。

Do not communicate by sharing memory; instead, share memory by communicating.

不要通过共享内存来通信,而应通过通信来共享内存。

本篇来聊 go-zero 对 Go 中 goroutine 反对的并发组件。

咱们回顾一下,go 原生反对的 goroutine 管制的工具有哪些?

  1. go func() 开启一个协程
  2. sync.WaitGroup 管制多个协程工作编排
  3. sync.Cond 协程唤醒或者是协程期待

那可能会问 go-zero 为什么还要拿出来讲这些?回到 go-zero 的设计理念:工具大于约定和文档

那么就来看看,go-zero 提供哪些工具?

threading

尽管 go func() 曾经很不便,然而有几个问题:

  • 如果协程异样退出,无奈追踪异样栈
  • 某个异样申请触发 panic,应该做故障隔离,而不是整个过程退出,容易被攻打

咱们看看 core/threading 包提供了哪些额定抉择:

func GoSafe(fn func()) {go RunSafe(fn)
}

func RunSafe(fn func()) {defer rescue.Recover()
    fn()}

func Recover(cleanups ...func()) {
    for _, cleanup := range cleanups {cleanup()
    }

    if p := recover(); p != nil {logx.ErrorStack(p)
    }
}

GoSafe

threading.GoSafe() 就帮你解决了这个问题。开发者能够将本人在协程中须要实现逻辑,以闭包的形式传入,由 GoSafe() 外部 go func()

当开发者的函数出现异常退出时,会在 Recover() 中打印异样栈,以便让开发者更快确定异样产生点和调用栈。

NewWorkerGroup

咱们再看第二个:WaitGroup。日常开发,其实 WaitGroup 没什么好说的,你须要 N 个协程合作:wg.Add(N),期待全副协程实现工作:wg.Wait(),同时实现一个工作须要手动 wg.Done()

能够看的进去,在工作开始 -> 完结 -> 期待,整个过程须要开发者关注工作的状态而后手动批改状态。

NewWorkerGroup 就帮开发者加重了累赘,开发者只须要关注:

  1. 工作逻辑【函数】
  2. 工作数【workers

而后启动 WorkerGroup.Start(),对应工作数就会启动:

func (wg WorkerGroup) Start() {
  // 包装了 sync.WaitGroup
    group := NewRoutineGroup()
    for i := 0; i < wg.workers; i++ {// 外部保护了 wg.Add(1) wg.Done()
    // 同时也是 goroutine 平安模式下进行的
        group.RunSafe(wg.job)
    }
    group.Wait()}

worker 的状态会主动治理,能够用来固定数量的 worker 来解决音讯队列的工作,用法如下:

func main() {group := NewWorkerGroup(func() {// process tasks}, runtime.NumCPU())
    group.Start()}

Pool

这里的 Pool 不是 sync.Poolsync.Pool 有个不不便的中央是 它池化的对象可能会被垃圾回收掉,这个就让开发者纳闷了,不晓得本人创立并存入的对象什么时候就没了。

go-zero 中的 pool

  1. pool 中的对象会依据应用工夫做懒销毁;
  2. 应用 cond 做对象生产和生产的告诉以及阻塞;
  3. 开发者能够自定义本人的生产函数,销毁函数;

那我来看看生产对象,和生产对象在 pool 中时怎么实现的:

func (p *Pool) Get() interface{} {
  // 调用 cond.Wait 时必须要持有 c.L 的锁
    p.lock.Lock()
    defer p.lock.Unlock()

    for {
    // 1. pool 中对象池是一个用链表连贯的 nodelist
        if p.head != nil {
            head := p.head
            p.head = head.next
      // 1.1 如果以后节点:以后工夫 >= 上次应用工夫 + 对象最大存活工夫
            if p.maxAge > 0 && head.lastUsed+p.maxAge < timex.Now() {
                p.created--
        // 阐明以后节点曾经过期了 -> 销毁节点对应的对象,而后持续寻找下一个节点
        //【⚠️:不是销毁节点,而是销毁节点对应的对象】p.destroy(head.item)
                continue
            } else {return head.item}
        }
        // 2. 对象池是懒加载的,get 的时候才去创建对象链表
        if p.created < p.limit {
            p.created++
      // 由开发者本人传入:生产函数
            return p.create()}
        
        p.cond.Wait()}
}
func (p *Pool) Put(x interface{}) {
    if x == nil {return}
    // 互斥拜访 pool 中 nodelist
    p.lock.Lock()
    defer p.lock.Unlock()

    p.head = &node{
        item:     x,
        next:     p.head,
        lastUsed: timex.Now(),}
  // 放入 head,告诉其余正在 get 的协程【极为要害】p.cond.Signal()}

上述就是 go-zeroCond 的应用。能够类比 生产者 - 消费者模型,只是在这里没有应用 channel 做通信,而是用 Cond。这里有几个个性:

  • Cond 和一个 Locker 关联,能够利用这个 Locker 对相干的依赖条件更改提供爱护。
  • Cond 能够同时反对 SignalBroadcast 办法,而 Channel 只能同时反对其中一种。

总结

工具大于约定和文档,始终是 go-zero 设计宗旨之一;也同时将平时业务积淀到组件中,这才是框架和组件的意义。

对于 go-zero 更多的设计和实现文章,能够继续关注咱们。欢送大家去关注和应用。

我的项目地址

https://github.com/tal-tech/go-zero

欢送应用 go-zero 并 star 反对咱们!

微信交换群

关注『微服务实际』公众号并回复 进群 获取社区群二维码。

go-zero 系列文章见『微服务实际』公众号

正文完
 0