V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
guonaihong
V2EX  ›  Go 编程语言

golang context 简介(3)-广播通知多个业务 go 程退出

  •  
  •   guonaihong ·
    guonaihong · 2019-09-25 09:20:28 +08:00 · 2825 次点击
    这是一个创建于 1685 天前的主题,其中的信息可能已经有所发展或是发生改变。

    上回聊到,context 是有父子间影响的,父 context cancel 之后,子 context 也会随之 cancel。 这次聊下广播通知多个业务 go 程退出

    如何通知多个业务 go 程退出

    问题描述

    如果在一个业务中会起多个 go 程做一些事,每个 go 程里面会跑流式业务(流式业务可以理解为 dong dong dong 有很多步骤)。你会如何做?

    1. 基于 bool flag 吗?那你要把所有的 select chan 的地方修改成轮训,改动太大,select 多路复用的优点全没了,引入 cpu 空转。关键有时候还不能这么修改

    2. 每次退出的时候发个事件到一个专门的 message chan? 基于计算的方式,万一少发了一个,就会造成 go 程泄漏。而且 message chan 的大小也是一个头疼的地方。

    3. sync.WaitGroup? WaitGroup 只是 go 程同步等待的函数,不是事件通知用的

    基于事件通知的做法

    在聊之前,大家估计都知道了两个事情,如果一个 chan 被 close 掉,是可以读取多次的,close 掉一个 close 掉的 chan 是会 panic 的。ok,基于这两点认知,我们设计通知多个业务 go 程的退出代码。

    ```go
    func testQuit(failId int, max int) {
    
        lock := sync.Mutex{}
        wg := sync.WaitGroup{}
        closeOk := false
        done := make(chan struct{})
    
        wg.Add(max)
        defer wg.Wait()
    
        for i := 0; i < max; i++ {
    
            go func(id int) {
                defer wg.Done()
    
                if id == failId || id == failId-1 {
                    lock.Lock()
    
                    if !closeOk {
                        close(done)
                        closeOk = true
                    }
    
                    lock.Unlock()
                }   
    
                select {
                case <-done:
                    fmt.Printf("id = %d, quit\n", id) 
                }
    
            }(i)
        }   
    }
    
    

    我们利用一个 chan 被 close 是可以读取多次的特性,用作广播通知。为了避免一个 chan 会 close 两次会 panic 加入一个 flag 变量。为了 go 程安全,引入 sync.Mutex。

    用 context 改造广播通知代码

    context 里面实现的代码也是用 close chan 的方式实现的,可能官方看大家都是用这种方式用作广播通知,最后把这种模式加入到了标准库里面

    package main
    
    import (
        "context"
        "fmt"
        "sync"
    )
    
    func testContext(failId int, max int) {
    
        var wg sync.WaitGroup
    
        ctx, cancel := context.WithCancel(context.Background())
    
        wg.Add(max)
        defer wg.Wait()
    
        for i := 0; i < max; i++ {
    
            go func(id int) {
                defer wg.Done()
                if id == failId {
                    cancel()
                }
    
                select {
                case <-ctx.Done():
                    fmt.Printf("id = %d, quit\n", id)
                }
    
            }(i)
        }
    }
    
    func main() {
        testContext(1, 5)
        testContext(0, 5)
    }
    
    

    我的 github

    https://github.com/guonaihong/gout

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1215 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 18:15 · PVG 02:15 · LAX 11:15 · JFK 14:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.