协程是Golang语言很重要的一个特色,它可以让你很方便的处理一些异步、耗时的任务。另外,在Golang中,创建一个协程很简单,你只要在调用的方法名前使用关键字go就可以。所以也正是因为这些原因,有时在Golang项目中,协程都快形成了一种滥用的情况。

协程虽然好用,但是如果用不好, 导致无法正常退出,随着时间的积累,会造成服务器内存耗尽,最终致使程序崩溃。

在Golang中,有两种常用的方法,可以优雅地控制一个Golang协程适时地退出

使用channel

Channel是Golang中的一个核心类型,你可以把它看成一个管道,通过它不同协程直接可以通过发送或者接收数据进行通讯。

Channel有几个特性:

  1. 如果没有设置容量,或者容量设置为0, 说明Channel没有缓存,只有sender和receiver都准备好了后它们的通讯才会发生。
  2. 如果设置了缓存,就有可能不发生阻塞, 只有buffer满了后send才会阻塞, 而只有缓存空了后receive才会阻塞。
  3. 当channel关闭后,可以receive,但不可以往里添加内容,而且receive的内容为通道类型的零值

而select是Golang中的一个控制结构,类似于用于switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。select 可以随机执行一个可运行的 case,如果没有 case 可运行,它将阻塞,直到有 case 可运行。

如下是结合for……select,通过channel来控制一个Golang协程适时地退出的示例。

如果上图所示,有两个协程,我们通过在一个协程中关闭channel,然后在另一个协程中判断是否关闭,来控制另一个协程的退出。

使用context

context.Context 是 Go 语言在 1.7 版本中引入的标准库的接口,它的作用是在不同协程之间同步请求特定数据、取消信号以及处理请求的截止日期。每一个 context.Context 都会从最顶层的 Goroutine 一层一层传递到最下层。

如下,我们利用context.Context,并结合for……select将协程关闭信号传到不同协程。

通过在一个协程中关闭上下文,然后在另一个协程中判断上下文是否关闭,来控制另一个协程的退出。