我创建了这个简单的应用程序来演示我遇到的问题。
package main
import (
"fmt"
"unsafe"
"sync"
)
type loc_t struct {
count [9999]int64
Counter int64
}
func (l loc_t) rampUp (wg *sync.WaitGroup) {
defer wg.Done()
l.Counter += 1
}
func main() {
wg := new(sync.WaitGroup)
loc := loc_t{}
fmt.Println(unsafe.Sizeof(loc))
wg.Add(1)
go loc.rampUp(wg)
wg.Wait()
fmt.Println(loc.Counter)
}
如果我执行上述操作,我将获得fatal error: newproc: function arguments too large for new goroutine
runtime stack:
runtime: unexpected return pc for runtime.systemstack called from 0x0
现在,当go
用于生成后台任务时,原因是2k堆栈大小。有趣的是我只传递一个被调用函数的指针。这个问题发生在我的生产中,显然不同的结构,一切都工作了一年,然后突然间它开始抛出这个错误。
答案 0 :(得分:3)
方法接收器传递给方法调用,就像任何其他参数一样。因此,如果该方法具有非指针接收器,则将复制您案例中的整个结构。最简单的解决方案是使用指针接收器,如果可以的话。
如果你必须使用非指针接收器,那么你可以通过不将方法调用作为goroutine而是另一个函数(可能是函数文字)启动来避免这种情况:
go func() {
loc.rampUp(wg)
}()
如果可以同时修改loc
变量(在启动goroutine之前进行调度并将其复制为rampUp()
方法),您可以手动创建它的副本并在goroutine中使用它,像这样:
loc2 := loc
wg.Add(1)
go func() {
loc2.rampUp(wg)
}()
这些解决方案有效,因为启动新的goroutine不需要大的初始堆栈,因此初始堆栈限制不会妨碍。并且堆栈大小是动态的,因此在启动后它将根据需要增长。详细信息可在此处阅读:Does Go have an "infinite call stack" equivalent?
答案 1 :(得分:1)
堆栈大小的问题显然是结构本身的大小。因此,当你的结构有机地增长时,你可以像我一样,跨越那个2k堆栈调用大小。
可以通过在函数声明中使用指向结构的指针来解决上述问题。
func (l *loc_t) rampUp (wg *sync.WaitGroup) {
defer wg.Done()
l.Counter += 1
}
这会创建一个指向结构的指针,因此进入堆栈的所有内容都是指针,而不是结构的整个副本。
显然,如果您同时在多个线程中进行调用,这可能会产生其他影响,包括竞争条件。但作为一个不断增长的结构的解决方案,它将突然开始导致堆栈溢出,这是一个解决方案。
无论如何,希望这对其他人有帮助。