Go 踩过的坑之协程参数不能过大
一、问题
在日常开发过程中,我们通常会使用协程来并发处理数据,比如下面的例子采用 协程 + sync.WaitGroup 来并发处理数据:
从mysql请求的切片数据,遍历后开启协程根据指定字段统计数据,此处采用了协程组的方式提高效率
代码如下:
//结构体
type RespStruct struct{
struct01 //内嵌另外的结构体
struct02 //内嵌另外的结构体
}
func StatisticData(){
var respData []RespStruct
var wg sync.WaitGroup
//1.从mysql查询数据,封装到respData ,代码省略
//2.循环respData并且开启协程组,添加协程
for i, d := range respData {
wg.Add(1)
go func(req ProduceOrderProcessQueryRespParam) {
defer func() {
wg.Done()
}()
//打印参数
fmt.Println(req)
return
}(d)
}
wg.wait()
fmt.Println("end.....")
}输出报错:
fatal error: newproc: function arguments too large for new goroutine
二、原因
由于结构体嵌结构体,变成新的比较大的结构体d。那么在启动新协程的时候,又因为是值传递,新copy了一份d的副本,导致参数超过了新goroutine的可用堆栈空间。 goroutine默认分配2k的内存
三、解决:
- 1.改为指针类型传递(推荐)
- 2.减少传递参数的大小,只传入需要使用的参数(推荐)
- 3.去除协程
四、修改:
将协程 的匿名函数修改为函数调用方式;代码修改如下
//结构体
type RespStruct struct{
struct01 //内嵌另外的结构体
struct02 //内嵌另外的结构体
}
func StatisticData(){
var respData []RespStruct
var wg sync.WaitGroup
//1.从mysql查询数据,封装到respData ,代码省略
//2.循环respData并且开启协程组,添加协程
for i, d := range respData {
wg.Add(1)
go func(req *ProduceOrderProcessQueryRespParam) {
defer func() {
wg.Done()
}()
//打印参数
fmt.Println(req)
return
}(&d)
}
wg.wait()
fmt.Println("end.....")
}正常输出
常见问题
Q1:大参数的“标准”是什么?多大算“过大”需要优化?
没有绝对标准,核心看“拷贝后是否触发内存压力”。
推荐两个参考值:
① 单个参数超过10MB(如100万条int切片);
② 协程数量多(超过10个)且参数超过1MB。
我通常在“参数>1MB且协程数>5”时就会优化,避免后期协程数增加导致OOM。
Q2:指针传参时,多个协程同时读会有并发安全问题吗?
仅读不写时绝对安全,因为指针只是指向数据,多个协程读不会修改数据;如果有协程写数据,就会出现数据竞争,需要加互斥锁(sync.Mutex)。示例:
var mu sync.Mutex
func processData(data *[]int) {
mu.Lock() // 加锁
(*data)[0] = 100 // 写操作
mu.Unlock() // 解锁
// 读操作无需加锁
fmt.Println(len(*data))
}Q3:通道传递大参数和指针传递,哪个性能更好?怎么选?
能上指针略优(少了通道的调度开销),但差异极小(微秒级),
选型主要看场景:
① 简单读场景(无并发写):选指针,更简洁;
② 有并发写或需要同步的场景(如生产者-消费者):选通道,天然安全;
③ 超大规模数据:选分块+通道,兼顾效率和安全。
总结
该报错的主要原因:新开 goroutine 的可用堆栈空间默认分配2k的内存 故传入的参数不宜过大,否则导致程序panic
解决方法也很简单,只需要通过指针引用的方式即可解决
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/go-learned-goroutine-1/
备用原文链接: https://blog.fiveyoboy.com/articles/go-learned-goroutine-1/