1 需求分析
go语言中通知子 goroutine 退出的三种方式
- 方式1 通过全局变量:如果全局变量为真就退出
- 方式2 通过通道:协程在通道里面取到true就退出
- 方式3 通过context:通过调用ctx.Done()方法通知所有的协程退出
- context.WithTimeout超时退出
2 通过全局变量和通道方式
全局变量方式存在的问题:
- 使用全局变量在跨包调用时不容易统一
- 如果worker中再启动goroutine,就不太好控制了。
管道方式存在的问题:
使用全局变量在跨包调用时不容易实现规范和统一,需要维护一个共用的channel
package main
import (
"fmt"
"sync"
"time"
)
//全局变量
var stopLabel bool
//全局通道
var stopChannel chan bool
var wg sync.WaitGroup
func test01() {
defer wg.Done()
for {
fmt.Println("test01")
time.Sleep(time.Millisecond * 10)
//如果全局变量stopLabel为真就退出
if stopLabel {
break
}
}
}
func test02() {
defer wg.Done()
//通过loop的标志位终止
loop:
for {
fmt.Println("test02")
time.Sleep(time.Millisecond * 30)
/*
监听的case中,没有满足条件的就阻塞
多个满足条件的就任选一个执行
select本身不带循环,需要外层的for
default通常不用,会产生忙轮询
break只能跳出select中的一个case
*/
select {
case <-stopChannel:
break loop
default:
}
}
//不可下面这种写法,chan是阻塞的
// for {
// fmt.Println("test02")
// time.Sleep(time.Millisecond * 30)
// if <-stopChannel {
// fmt.Println("test02退出")
// break
// }
// }
}
//方式1 通过全局变量
func globalExit() {
wg.Add(1)
go test01()
//5s以后就停止
time.Sleep(time.Second * 5)
stopLabel = true
wg.Wait()
}
//方式2 通过通道
func channelExit() {
//初始化通道
stopChannel = make(chan bool, 1)
wg.Add(1)
go test02()
//5s以后就停止
time.Sleep(time.Second * 5)
stopChannel <- true
wg.Wait()
}
func main() {
//go语言中通知子 goroutine 退出的三种方式
//方式1 通过全局变量
globalExit()
fmt.Println("方式1 通过全局变量 的方法结束")
//方式2 通过通道
channelExit()
//方式3 通过context
}
3 方式3 通过context
context的优势在于:
当子goroutine又开启另外一个goroutine时,只需要将ctx传入即可
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
//第2个协程
func test04(ctx context.Context) {
defer wg.Done()
loop:
for {
fmt.Println("test04")
time.Sleep(time.Millisecond * 10)
// 等待上级通知
select {
case <-ctx.Done():
break loop
default:
}
}
}
//第一个协程
func test03(ctx context.Context) {
defer wg.Done()
//第一个协程调用第2个协程 两个协程都会收到ctx的信号而终止
go test04(ctx)
loop:
for {
fmt.Println("test03")
time.Sleep(time.Millisecond * 10)
// 等待上级通知
select {
case <-ctx.Done():
break loop
default:
}
}
}
//方式3 通过context
func contextExit() {
wg.Add(2) //2个协程在跑
ctx, cancel := context.WithCancel(context.Background())
go test03(ctx)
//5s以后就停止
time.Sleep(time.Second * 3)
cancel() // 通知子goroutine结束
wg.Wait()
}
func main() {
//go语言中通知子 goroutine 退出的三种方式
//方式3 通过context
contextExit()
fmt.Println("方式3 通过context 结束")
}
4 context.WithTimeout超时退出
取消此上下文将释放与其相关的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel,通常用于数据库或者网络连接的超时控制。具体示例如下:
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func test02(ctx context.Context) {
defer wg.Done()
//通过loop的标志位终止
loop:
for {
//处理的事务
fmt.Println("test02")
time.Sleep(time.Millisecond * 30)
/*
监听的case中,没有满足条件的就阻塞
多个满足条件的就任选一个执行
select本身不带循环,需要外层的for
default通常不用,会产生忙轮询
break只能跳出select中的一个case
*/
select {
case <-ctx.Done():
break loop
default:
}
}
}
//超时退出
func timeoutExit() {
//3s就超时
timeLimit := time.Second * 3
ctx, cancel := context.WithTimeout(context.Background(), timeLimit)
wg.Add(1)
go test02(ctx)
//5s以后再回收相关联的资源
time.Sleep(time.Second * 5)
cancel()
wg.Wait()
}
func main() {
//超时退出
timeoutExit()
fmt.Println("超时退出 的方法结束")
}