Golang 退出 goroutine的几种方式

Posted 那天很冷

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang 退出 goroutine的几种方式相关的知识,希望对你有一定的参考价值。

传统方式

在刚开始学go的时候,没用过Context包,那么退出携程的方式一般有这么几种

使用携 chan 发送消息通知,这种一般只适合单个goroutine

func exit01() {
    done := make(chan bool)
    go func() {
        for {
            select {
            case <-done:
                fmt.Println("退出携程")
                return
            default:
                fmt.Println("监控中...")
                time.Sleep(1 * time.Second)
            }
        }
    }()
    time.Sleep(3 * time.Second)
    done <- true
    time.Sleep(5 * time.Second)
    fmt.Println("程序退出")
}

使用关闭 chan 的方式通知多个goroutine退出

func exit02() {
    done :=make(chan bool)
    go func() {
        for{
            select {
            case <-done:
                fmt.Println("退出携程01")
                return
            default:
                fmt.Println("监控01...")
                time.Sleep(1*time.Second)
            }
        }
    }()

    go func() {
        for res :=range done{ //没有消息阻塞状态,chan关闭 for 循环结束
            fmt.Println(res)
        }
        fmt.Println("退出监控03")
    }()

    go func() {
        for{
            select {
            case <-done:
                fmt.Println("退出携程02")
                return
            default:
                fmt.Println("监控02...")
                time.Sleep(1*time.Second)
            }
        }
    }()
    time.Sleep(3*time.Second)
    close(done)
    time.Sleep(5*time.Second)
    fmt.Println("退出程序")
}

初识 Context包

一个用于手动控制 goroutine 退出或者结束

获取 context上下文两种方式

ctx := context.Background() //这只能用于高等级(在 main 或顶级请求处理中)。这能用于派生我们稍后谈及的其他 context

ctx := context.TODO()  // 也只能用于高等级或当您不确定使用什么 context,或函数以后会更新以便接收一个 context 

他们的底层实现完全一致,不同的是,静态分析工具可以使用它来验证 context 是否正确传递,
这是一个重要的细节,因为静态分析工具可以帮助在早期发现潜在的错误,并且可以连接到 CI/CD 管道

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)

使用context.WithTimeout,主动调用 cancel()方法,可以在时间超时之前退出 goroutine

func exit03() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    go func() {
        for{
            select {
            case <-ctx.Done():
                fmt.Println("退出携程")
                return
            default:
                fmt.Println("请求中..")
                time.Sleep(1*time.Second)
            }
        }
    }()
    time.Sleep(5*time.Second)
    //cancel() //也可以手动调用 cancel()方法退出
    //time.Sleep(2*time.Second)
    fmt.Println("程序退出")

}

使用 context.WithCanel()方法,根据外部条件手动调用 cancel()方法退出
只有创建它的函数才能调用取消函数来取消此 context。如果您愿意,可以传递取消函数,但是,强烈建议不要这样做。
这可能导致取消函数的调用者没有意识到取消 context 的下游影响。可能存在源自此的其他 context,
这可能导致程序以意外的方式运行。简而言之,永远不要传递取消函数

func exit04() {
    ctx, cancel := context.WithCancel(context.Background())
    go func() {
        for{
            select {
            case <-ctx.Done():
                fmt.Println("退出携程")
                return
            default:
                fmt.Println("监控01")
                time.Sleep(1*time.Second)
            }
        }
    }()

    time.Sleep(5*time.Second)
    cancel()
    time.Sleep(2*time.Second)
    fmt.Println("退出程序")

}   

使用 context.WithDeadLine() ,在指定的时间退出 goroutine

func exit05() {
    stringTime := "2019-08-11 09:08:01"
    loc, _ := time.LoadLocation("Local")
    the_time, _ := time.ParseInLocation("2006-01-02 15:04:05", stringTime, loc)
    ctx, _ := context.WithDeadline(context.Background(), the_time)
    go func() {
        for{
            select {
            case <-ctx.Done():
                fmt.Println("退出 goroutine")
                return
            default:
                fmt.Println("监控...")
                time.Sleep(1*time.Second)
            }
        }
    }()

    time.Sleep(60*time.Second)
    fmt.Println("程序退出")

}

使用context.WithValue()传值,在所有的context树中都能获取到该值,如果设置相同的key 则覆盖该值
不建议使用 context 值传递关键参数,而是函数应接收签名中的那些值,使其显式化。

func exit06() {
    ctx := context.WithValue(context.Background(), "msg", "hello word")
    go func(ctx context.Context) {
        fmt.Println(ctx.Value("msg"))
    }(ctx)
    time.Sleep(2*time.Second)
    fmt.Println("程序退出")
}

相关建议约束

  • context.Background 只应用在最高等级,作为所有派生 context 的根。
  • context.TODO 应用在不确定要使用什么的地方,或者当前函数以后会更新以便使用 context。
  • context 取消是建议性的,这些函数可能需要一些时间来清理和退出。
  • context.Value 应该很少使用,它不应该被用来传递可选参数。这使得 API 隐式的并且可以引起错误。取而代之的是,这些值应该作为参数传递。
  • 不要将 context 存储在结构中,在函数中显式传递它们,最好是作为第一个参数。
  • 永远不要传递不存在的 context 。相反,如果您不确定使用什么,使用一个 ToDo context。
  • Context 结构没有取消方法,因为只有派生 context 的函数才应该取消 context。

参考:https://studygolang.com/articles/13866?fr=sidebar

以上是关于Golang 退出 goroutine的几种方式的主要内容,如果未能解决你的问题,请参考以下文章

golang学习随便记12

Golang读写文件的几种方式

golang读写文件的几种方式

golang基础-http请求的几种方式

golang基础-http请求的几种方式

Golang实践录:查询数据表的几种方式