GoLang之使goroutine停止的5种方法

1,872 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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