[TOC] # 简介 命令`go env`用于打印Go语言的环境信息。其中的一些信息我们在之前已经多次提及,但是却没有进行详细的说明。在本小节,我们会对这些信息进行深入介绍。我们先来看一看`go env`命令情况下都会打印出哪些Go语言通用环境信息。 `go env`命令可打印出的Go语言通用环境信息 ![](https://box.kancloud.cn/0f5f750020b9eba246bac2713429edf0_400x400.png) ~~~ > runtime 包 包含与 Go 的运行时系统交互的操作,例如控制 goroutines 的函数。 > 也包含 reflect 包使用的低级别的类型信息;查看 reflect 的文档了解运行时类型的可编程接口。 > 以下环境变量($name 或 %name% 取决于主机操作系统)控制 Go 程序的运行时行为,其含义和用途可能会随版本发布而变化 ~~~ # GOGC `GOGC`变量设置初始垃圾收集目标百分比。 当新分配的数据与上一次收集后剩余的活动数据的比率达到此百分比时,将触发收集。 默认值为`GOGC = 100`。 设置`GOGC = off`会完全禁用垃圾收集器。 `runtime/debug`包的`SetGCPercent`函数允许在运行时更改此百分比。 # GODEBUG `GODEBUG`变量控制运行时内的调试变量。 它是以逗号分隔的`name = val`对列表,用于设置这些命名变量: ~~~ allocfreetrace:设置 `allocfreetrace = 1` 会导致对每个对象的分配和释放进行概要分析和栈跟踪。 clobberfree:设置 `clobberfree = 1` 使垃圾回收器在释放对象时用错误内容破坏对象的内存内容。 cgocheck: 设置 `cgocheck = 0` 禁用使用 cgo 将 Go 指针错误传递到非 Go 代码的所有包检查。 设置 `cgocheck = 1`(默认值)可以启用相对便宜的检查,这些检查可能会遗漏一些错误。 设置 `cgocheck = 2` 启用昂贵的检查,这些检查不会遗漏任何错误,但是会导致程序运行速度变慢。 efence: 设置 `efence = 1` 会导致分配器以某种模式运行,在该模式下每个对象都分配在一个唯一的页面上,地址永远不会被回收。 gccheckmark: 设置 `gccheckmark = 1` 可以通过在 STW 时执行第二次标记传递来验证垃圾收集器的并发标记阶段。 如果第二次传递找到了一个并发标记未找到的可达对象,垃圾收集器将 panic。 gcpacertrace:设置 `gcpacertrace = 1` 会导致垃圾收集器打印有关并发 pacer 内部状态的信息。 gcshrinkstackoff: 设置 `gcshrinkstackoff = 1` 禁止将 goroutines 移动到较小的栈上。 在这种模式下,goroutine 的栈只能增长。 gcstoptheworld: 设置 `gcstoptheworld = 1` 禁用并发垃圾收集,使每个垃圾收集成为一个 STW 事件。 设置 `gcstoptheworld = 2` 还会在垃圾收集完成后禁用并发清除。 ~~~ ~~~ gctrace: 设置 `gctrace = 1` 会导致垃圾收集器在每次收集时向标准错误输出发出一行, 汇总收集的内存量和暂停的长度。此行的格式可能会发生变化。 目前格式是: gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P 其中字段含义如下('#' 代表数字): gc # 每次 GC 时递增的 GC 编号 @#s 程序运行的秒数 #% 自程序启动后在 GC 中花费的时间百分比 #+...+# GC 各阶段的挂钟时间(wall-clock)/CPU 时间 #->#-># MB GC 开始时的堆大小、GC 结束时的堆大小和活动堆大小 # MB goal 目标堆大小 # P 使用的处理器数量 这些阶段是 stop-the-world(STW)清除终止(sweep termination), 并发标记和扫描,以及 STW 标记终止(mark termination)。 `mark/scan` 的 CPU 时间被分解为辅助时间(根据分配执行的 GC)、后台 GC 时间和空闲 GC 时间。 如果该行以 `"(forced)"` 结尾,则此 GC 由 `runtime.GC()` 调用强制执行。 ~~~ **挂钟时间:** 根据计算机的内部时钟流逝的时间,这应该与外界的时间相匹配。 这与 CPU 使用率无关; 它仅供参考。 如果挂钟时间 < CPU 时间,那么您正在并行执行一个程序。 如果挂钟时间 > CPU时间,则表示您正在等待磁盘,网络或其他设备。 ~~~ 将 `gctrace` 设置为任何大于 0 的值也会导致垃圾收集器在将内存释放回系统时发出摘要。 将内存返回到系统的这个过程称为清除(`scavenging`)。 此摘要的格式可能会更改。 目前格式是: scvg#: # MB released printed only if non-zero scvg#: inuse: # idle: # sys: # released: # consumed: # (MB) 其中字段含义如下('#' 代表数字): scvg# 清除周期数,每次清除时递增 inuse: # MB 已使用或部分使用的 spans idle: # MB spans 待清除 sys: # 从系统映射的 MB released: # 释放到系统的 MB consumed: # 从系统中分配的 MB ~~~ ~~~ madvdontneed: 设置 madvdontneed = 1 当将内存返回到内核时,将在 Linux 上使用 MADV_DONTNEED 而不是 MADV_FREE。 这效率较低,但会导致 RSS(resident set size 常驻内存集)数量下降得更快。 ~~~ madvise()  系统调用允许一个了解其内存行为的进程将其描述给系统,给予使用内存的建议。 **int madvise(void \*addr, size\_t len, int advice);** 系统可以使用传入的建议来更改其虚拟内存分页策略。 此建议可以提高应用程序和系统性能。建议有很多种其中两种: **MADV\_DONTNEED:** 表示应用程序不希望很快访问此地址范围。 **MADV\_FREE:** 表示应用程序不需要此地址范围中包含的信息,因此可以立即重用这些页面。 地址范围仍然有效。 ~~~ memprofilerate: 设置 `memprofilerate = X` 将更新 runtime.MemProfileRate 的值。 设置为 0 时,禁用内存分析。 MemProfileRate 控制在内存概要文件中记录和报告的内存分配比例。 分析器的目标是对每 `MemProfileRate` 字节的平均分配进行抽样。 要在概要文件中包含每个已分配的块,请将 `MemProfileRate` 设置为 1。 要完全关闭分析,请将 `MemProfileRate` 设置为 0。 处理内存概要文件的工具假设概要文件速率在程序的整个生命周期中是恒定的,并且等于当前值。 更改内存分析速率的程序应该只改变一次,在程序执行过程中越早越好(例如,在 `main` 的开头)。 var MemProfileRate int = 512 * 1024 ~~~ ---- ~~~ invalidptr: 默认 `invalidptr = 1`,如果在指针类型的位置中发现无效的指针值(例如,1), 则会导致垃圾收集器和栈复制器使程序 crash。 设置 `invalidptr = 0` 将禁用此检查。 这应该只被用作诊断错误代码的临时解决方案。 真正的解决方案是不将整数存储在指针类型的位置。 ~~~ ~~~ sbrk: 设置 `sbrk = 1` 用一个简单的分配器替换内存分配器和垃圾收集器, 该分配器从操作系统获取内存并且永远不会回收任何内存。 ~~~ * * * ~~~ scavenge: `scavenge = 1` 启用堆清除程序的调试模式。 ~~~ * * * ~~~ scheddetail: 设置 `schedtrace = X` 和 `scheddetail = 1` 会导致调度程序每 `X` 毫秒发出一次详细的多行信息, 描述`调度程序`,`处理器`,`线程` 和 `goroutines` 的状态。 schedtrace: 设置 `schedtrace = X` 使调度程序每 `X` 毫秒发出一行标准错误,汇总调度程序状态。 ~~~ * * * ~~~ tracebackancestors: 设置 `tracebackancestors = N` 使用创建 `goroutines` 的栈扩展回溯, 其中 `N` 限制要报告的祖先 `goroutines` 的数量。 这也扩展了 `runtime.Stack` 返回的信息。 祖先的 `goroutine IDs` 将引用创建时 goroutine 的 ID; 这个 ID 有可能被重用于另一个 goroutine。 将 `N` 设置为 0 将不报告任何祖先信息。 ~~~ `net`,`net/http`和`crypto/tls` 也引用`GODEBUG`中的调试变量 # GOMAXPROCS `GOMAXPROCS`变量限制了可以同时执行用户级 Go 代码的操作系统线程数 (即,可以同时执行的最大`CPU`数)。 代表 Go 代码在系统调用中可以阻塞的线程数没有限制;那些不计入`GOMAXPROCS`限制。 该包的`GOMAXPROCS`函数查询并更改限制。 # GOTRACEBACK `GOTRACEBACK`变量控制 Go 程序因未恢复的`panic`或意外的运行时条件而失败时生成的输出量。 默认情况下,失败会打印当前`goroutine`的栈跟踪,省略运行时系统内部的函数,然后使用退出代码`2`退出。 如果当前`goroutine`或者故障都不在运行时内部,则故障会打印所有`goroutines`的栈跟踪。 * `GOTRACEBACK = none`完全省略了 goroutine 栈跟踪。 * `GOTRACEBACK = single`(默认值)的行为如上所述。 * `GOTRACEBACK = all`为用户创建的所有 goroutines 添加栈跟踪。 * `GOTRACEBACK=system`就像`"all"`,但为运行时函数添加了栈帧,并显示了运行时内部创建的 goroutine。 * `GOTRACEBACK=crash`就像`"system"`,但是以特定于操作系统的方式 crash 而不是退出。例如,在`Unix`系统上,`crash`引发`SIGABRT`以触发核心转储(`core dump`)。 由于历史原因,`GOTRACEBACK`设置`0`,`1`和`2`分别是`none`,`all`和`system`的同义词。 `runtime/debug`包的`SetTraceback`函数允许在运行时增加输出量,但不能减少到低于环境变量指定的输出量 # **CGO\_ENABLED** 通过上一小节的介绍,相信读者对cgo工具已经很熟悉了。我们提到过,标准go命令可以自动的使用cgo工具对导入了代码包C的代码包和源码文件进行处理。这里所说的“自动”并不是绝对的。因为当环境变量CGO\_ENABLED被设置为0时,标准go命令就不能处理导入了代码包C的代码包和源码文件了。请看下面的示例: ~~~ hc@ubt:~/golang/goc2p/src/basic/cgo$ export CGO_ENABLED=0 hc@ubt:~/golang/goc2p/src/basic/cgo$ go build -x WORK=/tmp/go-build775234613 ~~~ 我们临时把环境变量CGO\_ENABLED的值设置为0,然后执行`go build`命令并加入了标记`-x`。标记`-x`会让命令程序将运行期间所有实际执行的命令都打印到标准输出。但是,在执行命令之后没有任何命令被打印出来。这说明对代码包`basic/cgo`的构建操作并没有被执行。这是因为,构建这个代码包需要用到cgo工具,但cgo工具已经被禁用了。下面,我们再来运行调用了代码包`basic/cgo`中函数的命令源码文件cgo\_demo.go。也就是说,命令源码文件cgo\_demo.go间接的导入了代码包`C`。还记得吗?这个命令源码文件被存放在goc2p项目的代码包`basic/cgo`中。示例如下: ~~~ hc@ubt:~/golang/goc2p/src/basic/cgo$ export CGO_ENABLED=0 hc@ubt:~/golang/goc2p/src/basic/cgo$ go run -work cgo_demo.go WORK=/tmp/go-build856581210 # command-line-arguments ./cgo_demo.go:4: can't find import: "basic/cgo/lib" ~~~ 在上面的示例中,我们在执行`go run`命令时加入了两个标记——`-a`和`-work`。标记`-a`会使命令程序强行重新构建所有的代码包(包括涉及到的标准库),即使它们已经是最新的了。标记`-work`会使命令程序将临时工作目录的绝对路径打印到标准输出。命令程序输出的错误信息显示,命令程序没有找到代码包`basic/cgo`。其原因是由于代码包`basic/cgo`无法被构建。所以,命令程序在临时工作目录和工作区中都找不到代码包basic/cgo对应的归档文件cgo.a。如果我们使用命令`ll /tmp/go-build856581210`查看临时工作目录,也找不到名为basic的目录。 不过,如果我们在环境变量CGO\_ENABLED的值为1的情况下生成代码包`basic/cgo`对应的归档文件cgo.a,那么无论我们之后怎样改变环境变量CGO\_ENABLED的值也都可以正确的运行命令源码文件cgo\_demo.go。即使我们在执行`go run`命令时加入标记`-a`也是如此。因为命令程序依然可以在工作区中找到之前在我们执行`go install`命令时生成的归档文件cgo.a。示例如下: ~~~ hc@ubt:~/golang/goc2p/src/basic/cgo$ export CGO_ENABLED=1 hc@ubt:~/golang/goc2p/src/basic/cgo$ go install ../basic/cgo hc@ubt:~/golang/goc2p/src/basic/cgo$ export CGO_ENABLED=0 hc@ubt:~/golang/goc2p/src/basic/cgo$ go run -a -work cgo_demo.go WORK=/tmp/go-build130612063 The square root of 2.330000 is 1.526434. ABC CFunction1() is called. GoFunction1() is called. ~~~ 由此可知,只要我们事先成功安装了引用了代码包C的代码包,即生成了对应的代码包归档文件,即使cgo工具在之后被禁用,也不会影响到其它Go语言代码对该代码包的使用。当然,命令程序首先会到临时工作目录中寻找需要的代码包归档文件。 关于cgo工具还有一点需要特别注意,即:**当存在交叉编译的情况时,cgo工具一定是不可用的**。在标准go命令的上下文环境中,交叉编译意味着程序构建环境的目标计算架构的标识与程序运行环境的目标计算架构的标识不同,或者程序构建环境的目标操作系统的标识与程序运行环境的目标操作系统的标识不同。在这里,我们可以粗略认为交叉编译就是在当前的计算架构和操作系统下编译和构建Go语言代码并生成针对于其他计算架构或/和操作系统的编译结果文件和可执行文件。 # **GOARCH** GOARCH的值的含义是程序构建环境的目标计算架构的标识,也就是程序在构建或安装时所对应的计算架构的名称。在默认情况下,它会与程序运行环境的目标计算架构一致。即它的值会与GOHOSTARCH的值是相同。但如果我们显式的设置了环境变量GOARCH,则它的值就会是这个环境变量的值。 # **GOBIN** GOBIN的值为存放可执行文件的目录的绝对路径。它的值来自环境变量GOBIN。在我们使用`go tool install`命令安装命令源码文件时生成的可执行文件会存放于这个目录中。 # **GOCHAR** GOCHAR的值是程序构建环境的目标计算架构的单字符标识。它的值会根据GOARCH的值来设置。当GOARCH的值为386时,GOCHAR的值就是8。当GOARCH的值为amd64时GOCHAR的值就是6。而GOCHAR的值5与GOARCH的值arm相对应。 GOCHAR主要有两个用途,列举如下: 1. Go语言官方的平台相关的工具的名称会以它的值为前缀。的名称会以GOCHAR的值为前缀。比如,在amd64计算架构下,用于编译Go语言代码的编译器的名称是6g,链接器的名称是6l。用于编译C语言代码的编译器的名称是6c。而用于编译汇编语言代码的编译器的名称为6a。 2. Go语言的官方编译器生成的结果文件会以GOCHAR的值作为扩展名。Go语言的官方编译器6g在对命令源码文件编译之后会把结果文件*go*.6存放到临时工作目录的相应位置中。 # **GOEXE** GOEXE的值会被作为可执行文件的后缀。它的值与GOOS的值存在一定关系,即只有GOOS的值为“windows”时GOEXE的值才会是“.exe”,否则其值就为空字符串“”。这与在各个操作系统下的可执行文件的默认后缀是一致的。 # **GOHOSTARCH** GOHOSTARCH的值的含义是程序运行环境的目标计算架构的标识,也就是程序在运行时所在的计算机系统的计算架构的名称。在通常情况下,它的值不需要被显式的设置。因为用来安装Go语言的二进制分发文件和MSI(Microsoft软件安装)软件包文件都是平台相关的。所以,对于不同计算架构的Go语言环境来说,它都会是一个常量。 # **GOHOSTOS** GOHOSTOS的值的含义是程序运行环境的目标操作系统的标识,也即程序在运行时所在的计算机系统的操作系统的名称。与GOHOSTARCH类似,它的值在不同的操作系统下是固定不变的,同样不需要显式的设置。 # **GOPATH** 这个环境信息我们在之前已经提到过很多次。它的值指明了Go语言工作区目录的绝对路径。我们需要显式的设置环境变量GOPATH。如果有多个工作区,那么多个工作区的绝对路径之间需要用分隔符分隔。在windows操作系统下,这个分隔符为“;”。在其它操作系统下,这个分隔符为“:”。注意,GOPATH的值不能与GOROOT的值相同。 # **GORACE** GORACE的值包含了用于数据竞争检测的相关选项。数据竞争是在并发程序中最常见和最难调试的一类bug。数据竞争会发生在多个Goroutine争相访问相同的变量且至少有一个Goroutine中的程序在对这个变量进行写操作的情况下。 数据竞争检测需要被显式的开启。还记得标记`-race`吗?我们可以通过在执行一些标准go命令时加入这个标记来开启数据竞争检测。在这种情况下,GORACE的值就会被使用到了。支持`-race`标记的标准go命令包括:`go test`命令、`go run`命令、`go build`命令和`go install`命令。 GORACE的值形如“option1=val1 option2=val2”,即:选项名称与选项值之间以等号“=”分隔,多个选项之间以空格“ ”分隔。数据竞争检测的选项包括log\_path、exitcode、strip\_path\_prefix和history\_size。为了设置GORACE的值,我们需要设置环境变量GORACE。或者,我们也可以在执行go命令时临时设置它,像这样: ~~~ hc@ubt:~/golang/goc2p/src/cnet/ctcp$ GORACE="log_path=/home/hc/golang/goc2p /race/report strip_path_prefix=home/hc/golang/goc2p/" go test -race ~~~ 关于数据竞争检测的更多细节我们将会在本书的第三部分予以说明。 # **GOROOT** GOROOT会是我们在安装Go语言时第一个碰到Go语言环境变量。它的值指明了Go语言的安装目录的绝对路径。但是,只有在非默认情况下我们才需要显式的设置环境变量GOROOT。这里所说的默认情况是指:在Windows操作系统下我们把Go语言安装到c:\\Go目录下,或者在其它操作系统下我们把Go语言安装到/usr/local/go目录下。另外,当我们不是通过二进制分发包来安装Go语言的时候,也不需要设置环境变量GOROOT的值。比如,在Windows操作系统下,我们可以使用MSI软件包文件来安装Go语言。 # **GOTOOLDIR** GOTOOLDIR的值指明了Go工具目录的绝对路径。根据GOROOT、GOHOSTOS和GOHOSTARCH来设置。其值为$GOROOT/pkg/tool/$GOOS\_$GOARCH。关于这个目录,我们在之前也提到过多次。 除了上面介绍的这些通用的Go语言环境信息,还两个针对于非Plan 9操作系统的环境信息。它们是CC和GOGCCFLAGS。环境信息CC的值是操作系统默认的C语言编译器的命令名称。环境信息GOGCCFLAGS的值则是Go语言在使用操作系统的默认C语言编译器对C语言代码进行编译时加入的参数。 如果我们要有针对性的查看上述的一个或多个环境信息,可以在`go env`命令的后面加入它们的名字并执行之。在`go env`命令和环境信息名称之间需要用空格分隔,多个环境信息名称之间也需要用空格分隔。示例如下: ~~~ hc@ubt:~$ go env GOARCH GOCHAR 386 8 ~~~ 上例的`go env`命令的输出信息中,每一行对一个环境信息的值,且其顺序与我们输入的环境信息名称的顺序一致。比如,386为环境信息GOARCH,而8则是环境信息GOCHAR的值。 `go env`命令能够让我们对当前的Go语言环境进行简要的了解。通过它,我们也可以对当前安装的Go语言的环境设置进行简单的检查。