点击上方蓝色“Go语言中文网”关注我们,领全套Go资料,每天学习 Go 语言

ctx context.Context

使用 WaitGroup

学 Go 时肯定要学习如何使用并发(goroutine),而开发者该如何控制并发呢?其实有两种方式,一种是WaitGroup,另一种就是 context,而什么时候需要用到 WaitGroup 呢?很简单,就是当您需要将同一件事情拆成不同的 Job 进行执行,最后需要等到全部的 Job 都执行完成才继续执行主程序,这时候就需要用到 WaitGroup,看个实际例子

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup

    wg.Add(2)
    go func() {
        time.Sleep(2 * time.Second)
        fmt.Println("job 1 done.")
        wg.Done()
    }()
    go func() {
        time.Sleep(1 * time.Second)
        fmt.Println("job 2 done.")
        wg.Done()
    }()
    wg.Wait()
    fmt.Println("All Done.")
}
wg.Wait()

使用 channel + select

package main

import (
    "fmt"
    "time"
)

func main() {
    stop := make(chan bool)

    go func() {
        for {
            select {
            case <-stop:
                fmt.Println("got the stop channel")
                return
            default:
                fmt.Println("still working")
                time.Sleep(1 * time.Second)
            }
        }
    }()

    time.Sleep(5 * time.Second)
    fmt.Println("stop the gorutine")
    stop <- true
    time.Sleep(5 * time.Second)
}

上面可以看到,透过 select + channel 可以快速解决这个问题,只要在任何地方将 bool 值丢入 stop channel 就可以停止背景正在处理的 Job。上述用 channel 来解决此问题,但是现在有个问题,假设背景有跑了无数个 goroutine,或者是 goroutine 内又有跑 goroutine 呢,变得相当复杂,例如底下的状况

e25de01a8ae34477915d2e833a7eda1a.webp

这边就没办法用 channel 方式来进行处理了,而需要用到今天的重点 context。

认识 context

context.Background()
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    go func() {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("got the stop channel")
                return
            default:
                fmt.Println("still working")
                time.Sleep(1 * time.Second)
            }
        }
    }()

    time.Sleep(5 * time.Second)
    fmt.Println("stop the gorutine")
    cancel()
    time.Sleep(5 * time.Second)
}
context.WithCancel
ctx, cancel := context.WithCancel(context.Background())
cancel func
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    go worker(ctx, "node01")
    go worker(ctx, "node02")
    go worker(ctx, "node03")

    time.Sleep(5 * time.Second)
    fmt.Println("stop the gorutine")
    cancel()
    time.Sleep(5 * time.Second)
}

func worker(ctx context.Context, name string) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println(name, "got the stop channel")
            return
        default:
            fmt.Println(name, "still working")
            time.Sleep(1 * time.Second)
        }
    }
}

上面透过一个 context 可以一次停止多个 worker,看逻辑如何宣告 context 以及什么时机去执行 cancel(),通常我个人都是搭配 graceful shutdown 进行取消运转的工作,或者是停止资料库连线等等。。

心得

初学 Go 时,如果还不常使用 goroutine,其实也不会理解到 context 的使用方式及时机,等到需要有背景处理,以及该如何停止 Job,这时候才渐进了解到使用方式,当然 context 不只有这个使用方式,未来将会介绍其他使用方式。

原文链接:https://blog.wu-boy.com/2020/05/understant-golang-context-in-10-minutes/


推荐阅读


喜欢本文的朋友,欢迎关注“Go语言中文网”:

6dedbf9a2e610a19b09f7811f2da8dd4.webp