..

Golang:停止Goroutine有几种方法

goroutine介绍

goroutine 是 Go 语言实现并发编程的利器,是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理,简单的一个指令go function就能启动一个 goroutine;Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。

但是,Go语言并没有提供终止goroutine的接口,也就是说,我们不能从外部去停止一个goroutine,只能由goroutine内部退出(main函数终止除外);

goroutine 案例

日常开发开启一个 goroutine go代码, 只需一个 go 关键字即可:

var wg sync.WaitGroup // 等待组,用来阻塞程序

func main() {
	wg.Add(1) // 等待组 +1
	go func() {
		for {
			fmt.Println("开启 goroutine")
		}
	}()
	wg.Wait()
}

几种停止的办法

Go 目前用 channel 或 context 的方式来取消 (cancelation) 给 goroutine。

Channel

  • 第一种方法,就是借助 channel 的 close 机制来完成对 goroutine 的精确控制。
func main() {
	msg := make(chan struct{})
	go func() {
		fmt.Println("[go-routine] start")
		select {
		case <-msg:
			fmt.Println("[go-routine] done")
			return // 必须 return, 否则 goroutine 不会结束
		}
	}()
	fmt.Println("start")
	fmt.Println("close channel")
	close(msg) // 直接关闭通道 程序结束
	time.Sleep(1 * time.Second)
}
  • 第二种方法,通过定期轮训 channel 其结合了第一种方法和类似信号量的处理方式。
package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func worker(in, quit <-chan int) {
	defer wg.Done()
	for {
		select {
		case <-quit:
			fmt.Println("收到退出信号")
			return //必须return,否则goroutine不会结束
		case v := <-in:
			fmt.Println(v)
		}
	}
}

func main() {
	quit := make(chan int) //退出通道
	in := make(chan int)

	wg.Add(1)
	go worker(in, quit)

	for i := 0; i < 3; i++ {
		in <- i
		time.Sleep(1 * time.Second)
	}

	quit <- 1 //想通道写入退出信号
	wg.Wait()
}

Context

Go 语言的上下文(context)来做 goroutine 的控制和关闭, 对于是一种 tree 结构的goroutine, 要停止 sub tree 使用 channel 的方式不太方便

package main

import (
	"context"
	"errors"
	"fmt"
	"time"
)

func operation1(ctx context.Context) error {
	// 让我们假设这个操作会因为某种原因失败
	// 我们使用time.Sleep来模拟一个资源密集型操作
	time.Sleep(100 * time.Millisecond)
	return errors.New("failed")
}

func operation2(ctx context.Context) {
	// 我们使用在前面HTTP服务器例子里使用过的类似模式
	select {
	case <-time.After(500 * time.Millisecond):
		fmt.Println("done")
	case <-ctx.Done():
		fmt.Println("halted operation2")
	}
}

func main() {
	// 新建一个上下文
	ctx := context.Background()
	// 在初始上下文的基础上创建一个有取消功能的上下文
	ctx, cancel := context.WithCancel(ctx) //需要取消时,就调用cancel(),发出取消事件。
	// 在不同的goroutine中运行operation2
	go func() {
		operation2(ctx)
	}()

	err := operation1(ctx)
	fmt.Println(err)
	// 如果这个操作返回错误,取消所有使用相同上下文的操作
	if err != nil {
		cancel()
	}
}

参考

回答我,停止 Goroutine 有几种方法?

Go 如何去停止 goroutine

GoLang 使用 goroutine 停止的几种办法