..
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()
}
}