开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
GoLang之使goroutine停止的5种方法
1.goroutine停止介绍
- go语言没有提供终止goroutine的接口
2.goroutine停止的5种方法
2.1使用for-range:
for-range从channel上接收值,直到channel关闭;从单一管道上去获取数据执行任务
func worker1(ch chan int) {
defer wg.Done()
for value := range ch {
fmt.Println(value)
}
fmt.Println("get close signal")//没有close信号,这句话不会打出来
}
func CloseGoroutine1(){
ch := make(chan int)
wg.Add(1)
go worker1(ch)
for i := 0; i < 3; i++ {
ch <- i
}
close(ch) // 不close channel,worker1就会在for里面一直等待运行,程序无法退出
wg.Wait()
fmt.Println("close")
}
打印结果
0
1
2
get close signal
close
2.2使用for-select(向退出通道发出退出信号):
select能够让goroutine在多个通信操作上等待,监听多个channel。一般使用for死循环进行select信号监听,然后通过接收退出信号结束死循环。
func worker2(in, closeSignal <-chan int) {
defer wg.Done()
for {
select {
case <-closeSignal:
fmt.Println("收到关闭信号")
return //一定要退出,不然又是deadlock!
case v := <-in:
fmt.Println(v)
}
}
}
func CloseGoroutine2() {
in := make(chan int)
closeSignal := make(chan int) // 关闭整个channel的信号
wg.Add(1)
go worker2(in, closeSignal) // 将程序go起来
for i := 0; i < 3; i++ {
in <- i // 往channel里面推数据
}
closeSignal <- 1 // 结束这个goroutine,也可以替换为close(closeSignal)这个直接把所有的goroutine关闭
wg.Wait()
}
打印结果
0
1
2
收到关闭信号
2.3使用for-select(关闭退出通道)
在上面的方面中是一个信号量对应一个goroutine的关闭,如果同时开启了多个,可以直接使用close(closeSignal)将所有的gorotine关闭
代码参考
func worker3(in, closeSignal <-chan int) {
defer wg.Done()
for {
select {
case <-closeSignal:
fmt.Println("收到关闭信号")
return //一定要退出,不然又是deadlock!
case v := <-in:
fmt.Println(v)
}
}
}
func CloseGoroutine3() {
in := make(chan int)
closeSignal := make(chan int)
wg.Add(10)
for i := 0; i < 10; i++ {
go worker3(in, closeSignal)
in <- i
}
close(closeSignal)
wg.Wait()
}
打印结果
0
2
1
3
4
9
7
收到关闭信号
收到关闭信号
收到关闭信号
8
收到关闭信号
收到关闭信号
收到关闭信号
收到关闭信号
6
收到关闭信号
收到关闭信号
5
收到关闭信号
收到关闭信号
2.4使用for-select(关闭多个channel)
如果select上监听了多个通道,需要所有的通道都关闭后才能结束goroutine,这里就利用select的一个特性,select不会在nil的通道上进行等待,因此将channel赋值为nil即可,此外,还需要利用channel的ok值
func worker4(in1, in2 <-chan int) {
defer wg.Done()
for { //死循环等待,不等待会出错
select {
case v, ok := <-in1:
if !ok {
fmt.Println("in1 收到退出信号")
in1 = nil
} else {
fmt.Println("in1", v) // 正常推入,打印推入的值
}
case v, ok := <-in2:
if !ok {
fmt.Println("in2 收到退出信号")
in2 = nil
} else {
fmt.Println("in2", v)// 正常推入,打印推入的值
}
default: // 一定要有default避免死锁,一直等待,不退出循环
fmt.Println("正在等待通道输出信息") // 这里会一直多打印一些等待信号,不知道是怎么触发的
if in1 == nil && in2 == nil {
return // 跳出死循环
}
}
}
}
func CloseGoroutine4() {
in1 := make(chan int)
in2 := make(chan int)
wg.Add(1)
go worker4(in1, in2)
for i := 0; i < 3; i++ {
in1 <- 1
in2 <- 2
}
close(in1) //这里的close信号推进去,是由in1处理,但是ok是false,所以会打印出“in1 收到退出信号”
close(in2) // 同上
wg.Wait()
}
这里可以通过select_channel结合进行解决,通过close(channel) 内置函数,返回的形式 x,ok:=<-ch,其中通过 ok若为true则通道还未关闭,还在发送数据。若为fasle,则通道已经关闭,无数据发送。
这里最好是带有default,避免通道堵塞,造成死锁。
打印结果
in1 1
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
in2 2
in1 1
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
正在等待通道输出信息
in2 2
in1 1
in2 2
正在等待通道输出信息
正在等待通道输出信息
in1 收到退出信号
in2 收到退出信号
正在等待通道输出信息
2.5使用context包
context包是官方提供的一个用于控制多个goroutine写作的包;
使用context的cancel信号,可以终止goroutine的运行,context是可以向下传递的。
func operation1(ctx context.Context) error {
time.Sleep(100 * time.Millisecond)
return errors.New("failed")
}
func operation2(ctx context.Context) {
defer wg.Done()
for {
select {
case <-time.After(200 * time.Millisecond):
fmt.Println("done")
case <-ctx.Done():
fmt.Println("halted operation2")
return
default:
}
}
}
func CloseGoroutine5() {
// 新建一个上下文
ctx := context.Background()
// 在初始上下文的基础上创建一个有取消功能的上下文
cCtx, cancel := context.WithTimeout(ctx, 4*time.Second) //需要取消时,就调用cancel(),发出取消事件
// 在不同的goroutine中运行operation2
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
operation2(cCtx)
}()
}
err := operation1(cCtx)
// 如果这个操作返回错误,取消所有使用相同上下文的操作
if err != nil {
cancel()
}
wg.Wait() // 一定要wait,不然程序直接结束,打印不出halted operation2
}
打印结果
halted operation2
halted operation2
halted operation2