永远不要在不知道如何停止的情况下启动一个 goroutine
source link: https://www.cyningsun.com/01-31-2021/go-concurrency-goroutine-exit.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
永远不要在不知道如何停止的情况下启动一个 goroutine
在 Go 中,goroutine
的创建成本低,调度效率高,同时存在数十万个 goroutine
并不奇怪。虽然单个 goroutine
使用的内存有限,但是不意味着可以毫无限制的创建 goroutine
。
Never start a goroutine without knowing how it will stop
每次启动 goroutine
时,必须知道 goroutine
何时、如何退出。否则,程序就潜藏着内存泄漏问题。在讨论协程退出前,先了解下协程为何阻塞
协程阻塞无法自由退出,主要因为以下两点:
context
前者,很容易理解。一般来说启动 goroutine
处理事务,对于事务的处理完成时间都有一定的预期 举例:
- RPC调用:最大超时时间不会超过用户的等待时间
- 定时任务:执行一次的时间不应该超过启动的间隔
针对何时退出,Go 中 提供了 Context
用于 goroutine
生命周期管理
- Cancellation via context.WithCancel.
- Timeout via context.WithDeadline.
req, err := http.NewRequest("GET", "https://play.golang.org/", nil) if err != nil { log.Fatalf("%v", err) } ctx, cancel := context.WithTimeout(req.Context(), 1*time.Second) defer cancel() req = req.WithContext(ctx) client := http.DefaultClient resp, err := client.Do(req) if err != nil { log.Fatalf("%v", err) } fmt.Printf("%v\n", resp.StatusCode)
channel & select
后者,相对来说比较难理解一些。尤其是其他语言的使用者,对于他们而言,程序中的流程控制一般意味着:
- if/else
- for loop
在 Go 中,类似的理解仅仅对了一小半。因为 channel 和 select 才是流程控制的重点。
channel 提供了强大能力,帮助数据从一个 goroutine 流转到另一个 goroutine。也意味着,channel 对程序的 数据流
和 控制流
同时存在影响。
- closed 的 channel 永远不会阻塞
package main import "fmt" func main() { ch := make(chan bool, 2) ch <- true ch <- true close(ch) for v := range ch { fmt.Println(v) // called twice } }
- nil 的 channel 总是阻塞
package main func main() { var ch chan bool ch <- true // blocks forever }
- buffered/unbuffered channel 介于两者之间,会因为 channel 是否可以读写阻塞
那么究竟如何判断 channel 能否读写呢?答案就是 select。
select{
case channel_send_or_receive:
//Dosomething
case channel_send_or_receive:
//Dosomething
default:
//Dosomething
}
说了这么多,协程怎么退出呢?相信通过以上部分很容易得到结论:
- 根据 channel 可读状态返回
// 方式一:遍历关闭的 channel for x := range closedCh { fmt.Printf("Process %d\n", x) } // 方式二:Select 可读 channel for { select { case <-stopCh: fmt.Println("Recv stop signal") return case <-t.C: fmt.Println("Working .") } }
协程能够退出就够了么?还不够,完美的退出应该包含以下三点:
- 通知协程退出
- 通知确认,协程退出
- 获取协程最终返回的错误
举个例子:errgroup
func (g *Group) Wait() error { g.wg.Wait() if g.cancel != nil { g.cancel() } return g.err }
本文作者:cyningsun
本文地址: https://www.cyningsun.com/01-31-2021/go-concurrency-goroutine-exit.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK