Go 知识点(11) — goroutine 泄露、设置子协程退出条件

本文通过示例介绍了Go语言中goroutine失控和内存泄露的问题,提出了解决方案。首先,通过将goroutine创建移出循环避免无限制创建。然后,通过设置退出条件,如接收到特定信号,使goroutine能够有序退出,从而保持协程数量的可控。总结了使用协程的两个关键原则:避免无限制创建和确保协程有退出条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 问题现象

如果在开发过程中不考虑 goroutine 在什么时候能退出和控制 goroutine 生命期,就会造成 goroutine 失控或者泄露的情况 ,看示例代码:

func consumer(ch chan int) {
	for {
		data := <-ch
		fmt.Println(data)
	}
}

func main() {
	ch := make(chan int)
	for {
		var input string
		// 获取输入,模拟进程持续运行
		fmt.Scan(&input)
		go consumer(ch)
		// 输出现在的goroutine数量
		fmt.Println("goroutines 个数为:", runtime.NumGoroutine())
	}

}

运行程序,每输入一个字符串+回车,将会创建一个 goroutine, 结果如下 :

a
goroutines 个数为: 2
b
goroutines 个数为: 3
c
goroutines 个数为: 4
d
goroutines 个数为: 5
e
goroutines 个数为: 6
f
goroutines 个数为: 7
g
goroutines 个数为: 8
h
goroutines 个数为: 9
i
goroutines 个数为: 10

上面代码模拟一个进程根据需要创建 goroutine 的情况 。我们发现, 随着输入的字符串越来越多, goroutine 将会无限制地被创建,但并不会结束,因为 consumer 是个阻塞操作,且 channel 中没有让其退出的操作。如果一直持续下去将会造成内存大量分配,最终使进程崩溃。

那要如何解决呢?

2. 解决方案

2.1 创建单个子协程,在子协程中处理业务

修改上面代码,将创建子协程的代码挪动到循环外面。

func consumer(ch chan int) {
	for {
		data := <-ch
		fmt.Println("data is: ", data)
	}
}

func main() {
	ch := make(chan int)
	go consumer(ch) // 在循环外面创建协程
	for {
		var input string
		fmt.Scan(&input)
		fmt.Println("goroutines 个数为:", runtime.NumGoroutine())
	}

}

这样输出结果为:

a
goroutines 个数为: 2
v
goroutines 个数为: 2
x
goroutines 个数为: 2
w
goroutines 个数为: 2
f
goroutines 个数为: 2
g
goroutines 个数为: 2
b
goroutines 个数为: 2

从结果可以看到协程数量并没有随着收入字符的增多而增加,但是存在一个问题就是,子协程并没有退出的机制。

如何解决呢?接着往下看

2.2 设置子协程退出条件

在主协程中设置当输入字符串为 quit 时,往通道里面写入 -1,子协程从通道里面获取数据为 -1 时就退出。此时主协程仍然是有效的,但是子协程会永远退出,所以协程数量为 1 。

func consumer(ch chan int) {
	for {
		data := <-ch
		// 收到的数据为 -1 时,退出该循环,同时也会退出该协程
		if data == -1 {
			break
		}
		fmt.Println("data is: ", data)
	}

}

func main() {
	ch := make(chan int)
	go consumer(ch) // 在循环外面创建协程
	for {
		var input string
		fmt.Scan(&input)
		if input == "quit" {
			ch <- -1 // 当输入为 quit 时,往通道里面写入 -1
		}
		// 输出现在的goroutine数量
		fmt.Println("goroutines 个数为:", runtime.NumGoroutine())
	}
}

输出结果:

a
goroutines 个数为: 2
b
goroutines 个数为: 2
c
goroutines 个数为: 2
d
goroutines 个数为: 2
quit
goroutines 个数为: 2
a
goroutines 个数为: 1
b
goroutines 个数为: 1

3. 总结

从上面示例我们可以总结使用协程的一般原则:

  1. 尽量避免无限制的创建协程;
  2. 在需要反复创建协程的场景下,协程一定要有退出的条件,并且确保该退出条件能满足(即代码能执行到);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wohu007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值