context介绍
Golang里的Context包非常重要,Context包是在golang 1.7之后引入的,它主要是解决如何优雅的去控制子goroutine退出。
控制并发的两种方式
1. Waitgroup:
适用于多个goroutine执行同一件事,然后一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。比如将一个大的任务拆解多个子任务。
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup wg.Add(2) //job 1 go func(){ //暂停2秒 time.Sleep(2 * time.Second) fmt.Println("job 1 done") wg.Done()//上面执行完毕后就会执行done }() //job 2 go func(){ //暂停1秒 time.Sleep(1 * time.Second) fmt.Println("job 2 done") wg.Done() }() wg.Wait()//wait一直在阻塞直到等待所有job都完成 fmt.Println("All Done") }
执行结果
songguojundeMacBook-Pro:context songguojun$ go run main.go job 2 done job 1 done All Done
上面的例子每个job都是自己跑直到跑完任务结束,如果外界要中断其执行,看看下面的例子。
package main import ( "fmt" "time" "sync" ) var wg sync.WaitGroup var quitOut bool func worker() { defer wg.Done() //死循环 for { fmt.Println("for running.") time.Sleep(1 * time.Second) if quitOut {//达到条件就退出 break } } } func main() { wg.Add(1) go worker() time.Sleep(5 * time.Second) //设置quitOut为true通知子goroutine退出 quitOut = true wg.Wait() fmt.Println("running closed") }
执行结果
songguojundeMacBook-Pro:context songguojun$ go run main.go for running. for running. for running. for running. for running. running closed
下面的例子使用的是channel + select的方式。
package main import ( "fmt" "time" ) func main() { stop := make(chan bool) //chan初始化 go func() { for { select { //接受信息 case <-stop: fmt.Println("got the stop channel") return //这里如果写break只能跳出select而不能跳出for循环 default: fmt.Println("still working") time.Sleep(1 * time.Second) } } }() time.Sleep(5 * time.Second) fmt.Println("stop the gorutine") //将true传递给stop stop <- true time.Sleep(5 * time.Second) }
执行结果
songguojundeMacBook-Pro:context songguojun$ go run main.go
still working
still working
still working
still working
still working
stop the gorutine
got the stop channel #下面没有输出,就是已经结束gorutine了
2. Context
Context用于在一个主gorutine去通知子gorutine结束工作,上面的代码例子中,如果gorutine里嵌套gorutine,就是一个job中又包含一个job,这时候这种场景就需要用到Context。
package main import ( "fmt" "time" "context" ) func worker(ctx context.Context, name string) { go func() { for { select { case <-ctx.Done(): //Done是一个只读的chan fmt.Println(name, "got the stop channel") return default: fmt.Println(name, "still working") time.Sleep(1 * time.Second) } } }() } func main() { ctx, cancel := context.WithCancel(context.Background()) //context.WithCancel表示创建一个取消函数 go worker(ctx, "node1") go worker(ctx, "node2") go worker(ctx, "node3") time.Sleep(5 * time.Second) fmt.Println("stop the gorutine") //cancel方法可以将上面node1 node2 node3都停止掉 cancel() time.Sleep(5 * time.Second) }
执行结果
songguojundeMacBook-Pro:context songguojun$ go run main.go
node3 still working
node1 still working
node2 still working
node2 still working
node1 still working
node3 still working
node2 still working
node1 still working
node3 still working
node3 still working
node1 still working
node2 still working
node2 still working
node3 still working
node1 still working
stop the gorutine
node1 got the stop channel
node2 got the stop channel
node3 got the stop channel