struct中的线程,函数参数对于新的goroutine来说太大

时间:2018-06-12 16:25:54

标签: go struct stack runtime goroutine

我创建了这个简单的应用程序来演示我遇到的问题。

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堆栈大小。有趣的是我只传递一个被调用函数的指针。这个问题发生在我的生产中,显然不同的结构,一切都工作了一年,然后突然间它开始抛出这个错误。

2 个答案:

答案 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
}

这会创建一个指向结构的指针,因此进入堆栈的所有内容都是指针,而不是结构的整个副本。

显然,如果您同时在多个线程中进行调用,这可能会产生其他影响,包括竞争条件。但作为一个不断增长的结构的解决方案,它将突然开始导致堆栈溢出,这是一个解决方案。

无论如何,希望这对其他人有帮助。