【问题标题】:Thread within struct, function arguments too large for new goroutine结构中的线程,函数参数对于新的 goroutine 来说太大了
【发布时间】:2018-06-12 16:25:53
【问题描述】:

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

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

【问题讨论】:

  • 是的,方法接收器也像任何其他参数一样被传递。用指针接收器就好了。

标签: go struct stack runtime goroutine


【解决方案1】:

方法接收器被传递给方法调用,就像任何其他参数一样。因此,如果该方法具有非指针接收器,则将复制您的整个结构。如果可以的话,最简单的解决方案是使用指针接收器。

如果您必须使用非指针接收器,那么您可以通过不将方法调用作为 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?

【讨论】:

    【解决方案2】:

    显然,堆栈大小的问题在于结构本身的大小。因此,随着您的结构有机地增长,您可以像我一样跨越 2k 堆栈调用大小。

    上述问题可以通过在函数声明中使用指向结构体的指针来解决。

    func (l *loc_t) rampUp (wg *sync.WaitGroup) {
        defer wg.Done()
        l.Counter += 1
    }
    

    这会创建一个指向结构体的指针,因此进入堆栈的只是指针,而不是结构体的整个副本。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-28
      • 2021-06-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多