结构中的线程,函数参数对于新的 goroutine 来说太大
Thread within struct, function arguments too large for new 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 堆栈大小。有趣的是我只传递一个指针给被调用的函数。这个问题在生产中发生在我身上,显然不同的结构,一切都工作了一年,然后突然开始抛出这个错误。
堆栈大小的问题显然是结构本身的大小。因此,随着您的结构有机增长,您可以像我一样跨越 2k 堆栈调用大小。
可以通过在函数声明中使用指向结构的指针来解决上述问题。
func (l *loc_t) rampUp (wg *sync.WaitGroup) {
defer wg.Done()
l.Counter += 1
}
这会创建一个指向结构的指针,因此进入堆栈的只是指针,而不是结构的整个副本。
显然,如果您同时在多个线程中进行调用,这可能会产生其他影响,包括竞争条件。但作为一个不断增长的结构的解决方案,它会突然开始导致堆栈溢出,这是一个解决方案。
无论如何,希望这对其他人有帮助。
方法接收器被传递给方法调用,就像任何其他参数一样。因此,如果该方法具有非指针接收器,则将复制您案例中的整个结构。如果可以的话,最简单的解决方案是使用指针接收器。
如果您必须使用非指针接收器,那么您可以通过不将方法调用作为 goroutine 而是另一个函数(可能是函数文字)来启动方法调用来规避此问题:
go func() {
loc.rampUp(wg)
}()
如果 loc
变量可能被同时修改(在启动的 goroutine 被调度并为 rampUp()
方法复制它之前),您可以手动创建它的副本并在goroutine,像这样:
loc2 := loc
wg.Add(1)
go func() {
loc2.rampUp(wg)
}()
这些解决方案之所以有效,是因为启动新的 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 堆栈大小。有趣的是我只传递一个指针给被调用的函数。这个问题在生产中发生在我身上,显然不同的结构,一切都工作了一年,然后突然开始抛出这个错误。
堆栈大小的问题显然是结构本身的大小。因此,随着您的结构有机增长,您可以像我一样跨越 2k 堆栈调用大小。
可以通过在函数声明中使用指向结构的指针来解决上述问题。
func (l *loc_t) rampUp (wg *sync.WaitGroup) {
defer wg.Done()
l.Counter += 1
}
这会创建一个指向结构的指针,因此进入堆栈的只是指针,而不是结构的整个副本。
显然,如果您同时在多个线程中进行调用,这可能会产生其他影响,包括竞争条件。但作为一个不断增长的结构的解决方案,它会突然开始导致堆栈溢出,这是一个解决方案。
无论如何,希望这对其他人有帮助。
方法接收器被传递给方法调用,就像任何其他参数一样。因此,如果该方法具有非指针接收器,则将复制您案例中的整个结构。如果可以的话,最简单的解决方案是使用指针接收器。
如果您必须使用非指针接收器,那么您可以通过不将方法调用作为 goroutine 而是另一个函数(可能是函数文字)来启动方法调用来规避此问题:
go func() {
loc.rampUp(wg)
}()
如果 loc
变量可能被同时修改(在启动的 goroutine 被调度并为 rampUp()
方法复制它之前),您可以手动创建它的副本并在goroutine,像这样:
loc2 := loc
wg.Add(1)
go func() {
loc2.rampUp(wg)
}()
这些解决方案之所以有效,是因为启动新的 goroutine 不需要大的初始堆栈,因此初始堆栈限制不会妨碍。堆栈大小是动态的,因此在启动后它会根据需要增长。详情可以在这里阅读: