go 运行时:可能在峰值负载后,所有组件都应该缩小,

qcuzuvrc  于 5个月前  发布在  Go
关注(0)|答案(6)|浏览(49)

你正在使用的Go版本是什么( go version )?

$ go version
go version go1.12.4 linux/amd64

这个问题在最新版本中是否会重现?

Y

你正在使用什么操作系统和处理器架构( go env )?

any

你做了什么?

当系统处理高峰负载时,它会创建很多goroutines,之后这些goroutine的垃圾回收会导致更多的CPU消耗。
可以通过以下方式复现:

package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
	"time"
)

func sayhello(wr http.ResponseWriter, r *http.Request) {}

func main() {
	for i := 0; i < 1000000; i++ {
		go func() {
			time.Sleep(time.Second * 10)
		}()
	}
	http.HandleFunc("/", sayhello)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

在10秒后,inuse对象仍然保持不变。

你期望看到什么?

全局goroutines缩小到合适的大小。

你实际上看到了什么?

由malg创建的许多inuse对象

tktrz96b

tktrz96b1#

实际上,所有的东西都没有减少,这不利于稳定性,应该提供一种减少策略,比如sysmon监控发现超过一半的g都死了,然后释放它。

ego6inou

ego6inou3#

你的观察是正确的。目前,运行时从未释放为goroutines创建的g对象,尽管它会重用它们。主要原因是调度器经常在没有写屏障的情况下操作g指针(许多调度器代码在没有P的情况下运行,因此不能有写屏障),这使得很难确定何时可以回收g。
一个可能的解决方案是使用一种类似于RCU的回收机制,该机制可以在Ms中理解每个M的调度器何时通过静止状态。然后我们可以在所有Ms都处于静止状态后的宽限期后安排未使用的gs被回收。不幸的是,我们不能简单地使用STW来检测这个宽限期,因为那些会停止所有的P,所以,就像写屏障一样,它们不会保护调度器示例在没有P的情况下操作gs。
@changkun,我不确定你的基准测试衡量的是什么。从RunParallel内部调用runtime.GC是没有意义的。垃圾收集器已经是并发的,从runtime.GC调用runtime.GC并不会在第一个完成之前启动另一个垃圾收集。此外,如果有几个待处理的runtime.GC调用,它们都会合并成一个单独的GC。如果意图只是测量GC需要多长时间,只需在不带RunParallel的情况下调用runtime.GC即可。

vvppvyoh

vvppvyoh4#

RunParallel 中调用 runtime.GC 不测量 allglock 上的争用。GCs 由 runtime.GC 本身串行化,因此它们不会争夺 allglock ,并且它们由 runtime.GC 合并,因此调用 runtime.GC N 次并发可能导致从 1 到 N 个 GCs,具体取决于调度的变数。

抛开基准测试不谈,我认为我们都清楚 allgs 永远不会被收集,这会影响 GC 时间和堆大小。

由于 gs 只是堆分配的,所以在 GC 期间收集它们是最有意义的,就像其他堆分配一样。问题是什么时候安全地将它们从 allgs 中解除链接并允许它们被收集,考虑到正常的 GC 可达性不变式不适用于 gs(同时,我们也不希望过于激进地将它们从 allgs 中解除链接,因为我们希望分配池行为降低启动 goroutine 的成本)。这当然是可行的,但需要相当多的关注。

oknwwptz

oknwwptz5#

每次都延迟运行时垃圾回收?

lsmd5eda

lsmd5eda6#

@matteo-gz 在基准测试之间运行GC。有关提问,请参阅:

相关问题