go Context的使用

控制并发有两种经典的方式,一种是WaitGroup,另一种就是Context安全

WaitGroup的使用

  • WaitGroup能够用来控制多个goroutine同时完成
func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        time.Sleep(2*time.Second)
        fmt.Println("1号完成")
        wg.Done()
    }()
    go func() {
        time.Sleep(2*time.Second)
        fmt.Println("2号完成")
        wg.Done()
    }()
    wg.Wait()
    fmt.Println("好了,你们都干完了,放工")
}
以上例子必定要等到两个goroutine同时作完才会所有完成,这种控制并发方式尤为适用于多个goroutine协同作一件事情的时候。

chan通知

  • chan也能够用于控制goroutine,经过chan来控制goroutine是否结束
func main() {
    stop := make(chan bool)

    go func() {
        for {
            select {
            case <-stop:
                fmt.Println("监控退出,中止了...")
                return
            default:
                fmt.Println("goroutine监控中...")
                time.Sleep(2 * time.Second)
            }
        }
    }()

    time.Sleep(10 * time.Second)
    fmt.Println("能够了,通知监控中止")
    stop<- true
    //为了检测监控过是否中止,若是没有监控输出,就表示中止了
    time.Sleep(5 * time.Second)
}
例子中咱们经过select判断stop是否接受到值,若是接受到值就表示能够推出中止了,若是没有接受到,就会执行default里面的监控逻辑,继续监控,直到收到stop的通知

以上控制goroutine的方式在大多数状况下能够知足咱们的使用,可是也存在不少局限性,好比有不少goroutiine,而且这些goroutine还衍生了其余goroutine,此时chan就比较困难解决这样的问题了

Context

以上问题是存在的,好比一个网络请求request,每一个request都须要开启一个goroutine作一些事情。因此咱们须要一种能够跟踪goroutine的方案才能够达到控制的目的,go为咱们提供了Context网络

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go watch(ctx,"【监控1】")
    go watch(ctx,"【监控2】")
    go watch(ctx,"【监控3】")
    time.Sleep(10 * time.Second)
    fmt.Println("能够了,通知监控中止")
    cancel()
    //为了检测监控过是否中止,若是没有监控输出,就表示中止了
    time.Sleep(5 * time.Second)
}
func watch(ctx context.Context, name string) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println(name,"监控退出,中止了...")
            return
        default:
            fmt.Println(name,"goroutine监控中...")
            time.Sleep(2 * time.Second)
        }
    }
}

例子中启动了3个监控goroutine进行不断的监控,每个都使用Context进行跟踪,当咱们使用cancel函数通知取消时候,这3个 goroutine都会被结束。全部基于这个context或者衍生出来的子Context都会收到通知,这样就能够进行清理操做最终释放goroutine了并发

Context接口

Context是一个接口,具体的内容以下:
~go
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
~函数

  • Deadline方法是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,若是须要取消的话,须要调用取消函数进行取消
  • Done方法返回一个只读的chan,类型为struct{},咱们在goroutine中,若是该方法返回的chan能够读取,则意味着parent context已经发起了取消请求,咱们经过Done方法收到这个信号后,就应该作清理操做,而后退出goroutine,释放资源
  • Err方法返回取消的错误缘由,由于什么Context被取消。
  • Value方法获取该Context上绑定的值,是一个键值对,因此要经过一个Key才能够获取对应的值,这个值通常是线程安全的

Context的继承衍生

  • context包为咱们提供的With系列的函数了
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

这四个With函数,接收的都有一个partent参数,就是父Context,咱们要基于这个父Context建立出子Context的意思线程

  • WithCancel函数,传递一个父Context做为参数,返回子Context,以及一个取消函数用来取消Context
  • WithDeadline函数,和WithCancel差很少,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,固然咱们也能够不等到这个时候,能够提早经过取消函数进行取消
  • WithTimeout和WithDeadline基本上同样,这个表示是超时自动取消,是多少时间后自动取消Context的意思
  • WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据能够经过Context.Value方法访问到

Context使用原则

  1. 不要把Context放在结构体中,要以参数的方式进行传递
  2. 以Context做为参数的函数方法,应该把Context做为第一个参数,放在第一位
  3. 给一个函数方法传递Context的时候,不要传递nil,若是不知道传递什么,就使用context.TODO
  4. Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递
相关文章
相关标签/搜索