goroutine的退出机制,不能通过某种手段强制关闭,只能等goroutine主动退出。
常用三种方式:
一、 通道 有 chan, for range、 for select
-
for-range, 当无缓存通道关闭时,for-range 自动退出。 需要close(chan) , 否则会死锁。
-
使用 select ok 退出
- for-select也是使用频率很高的结构,select提供了多路复用的能力,所以for-select可以让函数具有持续多路处理多个channel的能力。但select没有感知channel的关闭,这引出了2个问题:
-
继续在关闭的通道上读,会读到通道传输数据类型的零值,如果是指针类型,读到nil,继续处理还会产生nil。
-
继续在关闭的通道上写,将会panic。
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan bool)
go func() {
for {
select {
case <-done:
fmt.Println("退出协程01")
return
default:
fmt.Println("监控01 ....")
time.Sleep(1 * time.Second)
}
}
}()
go func() {
for res := range done {
fmt.Println(res) //没有消息则是阻塞状态 //chan 关闭则for循环结束
}
fmt.Println("退出监控03")
}()
go func() {
for {
select {
case <-done:
fmt.Println("退出协程02")
return
default:
fmt.Println("监控02.。。")
time.Sleep(1 * time.Second)
}
}
}()
time.Sleep(3 * time.Second)
close(done)
time.Sleep(5 * time.Second)
fmt.Println("退出程序")
}
二、 context
初识 Context包
一个用于手动控制 goroutine 退出或者结束
获取 context上下文两种方式
ctx := context.Background() //这只能用于高等级(在 main 或顶级请求处理中)。这能用于派生我们稍后谈及的其他 context
ctx := context.TODO() // 也只能用于高等级或当您不确定使用什么 context,或函数以后会更新以便接收一个 context
使用 context.WithTimeout,主动调用 cancel()方法,可以在时间超时之前退出 goroutine
使用 context.WithCanel()方法,根据外部条件手动调用 cancel()方法退出
使用 context.WithDeadLine() ,在指定的时间退出 goroutine
使用 context.WithValue()传值,在所有的context树中都能获取到该值,如果设置相同的key 则覆盖该值
package main
import (
"context"
"fmt"
"time"
)
func exit04() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("退出协程")
return
default:
fmt.Println("监控01")
time.Sleep(1 * time.Second)
}
}
}()
time.Sleep(5 * time.Second)
cancel()
time.Sleep(2 * time.Second)
fmt.Println("退出程序")
}
func main() {
exit04()
}