两个对象:垃圾回收器与调度器
并非整体
Go 程序构建后,是一个二进制文件。二进制看起来是一个整体,然而却是由几个组件组成。如果撬开二进制坚硬外壳,你会看到两个组件:调度器与垃圾回收器。
每一个 Go 开发者,至少会在两个时刻碰到调度器或垃圾回收器。在面试时,你被问到垃圾回收器原理。被问得面红耳赤,自信的音量滑到底。或者当你有上万个应用时,需要性能优化。每个应用节省5%性能,一万个应用节省 500。假设一个应用需要2个CPU 核心,一万个应用节省1000个 CPU 核心。
调度器由 GMP、Thread、Heap 组成,它的工作是调度goroutine
。调度器是三岔路口的交警,疏导阻goroutine
运行,使得程序飞速运转。垃圾回收器也是由 G Thread 组成,它的工作是释放内存。垃圾回收器如同后勤保障部,负责扫描并释放内存,保障 Go 程序有足够运行的内存空间。
两个对象
如果你想优化一个程序,首先要找到优化对象。最好是可观察、可测量、可操作的对象,这样你才能优化它。而且,可观察、可测量、可操作程度越高,越容易有效优化。垃圾回收器与调度器,是你能观察、测量、操作的对象。
对象是抽象系统的猫,以至于数学家都需要对象。数学家将数学中的各种东西抽象成对象:点是对象,线是对象,面也是对象;集合是对象,变量是对象,函数也是对象;数学家在头脑中,移动点对象,折弯线对象,变换函数对象。如果没有对象,数学家会是何等无助。
与数学相同,程序系统也是抽象的,所以开发者也需要对象。垃圾回收器与调度器,是 Go 开发者需要的对象。你能调高垃圾回收百分比参数,降低垃圾回收频率,让程序飞快。或者调小垃圾回收百分比,让 Go 程序在小内存服务器上可靠运行。
开发者需要对象,有对象的开发者是幸福的。
调试对象
如何调试调度器与垃圾回收器?
一,通过标准包runtime
提供的接口操作这两个对象。runtime
包实现了调度器和垃圾回收器,并提供了操作它们的接口。调度器接口清单如下:
goroutine
`debug.SetMaxStack(20480)`:设置单个 goroutine stack 可使用的最大内存值。
`Mutex`
Thread
`debug.SetMaxThreads(10)`:设置 go 程序可以使用的最大操作系统线程数。
`runtime.LockOSThread()`:使当前 goroutine 占有当前操作系统线程,直到调用 `runtime.UnlockOSThread()` ,或者 goroutine 退出。
垃圾回收器的接口清单如下:
debug.FreeOSMemory()
:强制垃圾回收器执行垃圾回收。debug.SetGCPercent(200)
:设置垃圾回收百分比,垃圾回收百分比是影响垃圾回收触发的条件。也可通过环境变量GOGC
设置百分比。debug.ReadGCStats(stats)
:获取上次垃圾回收信息。forcegcperiod
:垃圾回收时间间隔,在proc.go
中定义。默认值两分钟,每两分钟执行一次垃圾回收,如果在两分钟内没有其它条件触发垃圾回收。
通过清单中的接口,你能调试垃圾回收器与调度器,解决程序重大漏洞,或者优化程序性能。据说,Uber 公司通过垃圾回收接口,实现半自动优化垃圾回收,从而节省了7万个CPU内核。
二,通过pprof
包测量对象。 pprof 包提供了运行时剖面数据,数据格式是 pprof 可视化工具期望的。通过 pprof 包,测量对象的运行表现。表现好,停止操作;表现不好,接着操作。
后记
有很多文章,讲解调度器与垃圾回收器。他们很详实,也很细致。但是,他们太抽象了。或许我们需要对象,帮助我们理解抽象系统。我们也需要接口,来操作对象,在操作中理解。直到对象在心中化形。
Day:2022年1月26日