Go 1.4 发布说明

Go 1.4 简介

最新的 Go 版本 1.4 按照计划在 1.3 发布六个月后发布。

它只包含一个微小的语言更改,以向后兼容的 for-range 循环简单变体形式出现,以及一个可能导致编译器中断的更改,涉及指向指针的指针上的方法。

此版本主要侧重于实现工作,改进垃圾回收器并为在未来几个版本中推出的完全并发收集器做好准备。栈现在是连续的,在必要时重新分配而不是链接到新的“段”;因此,此版本消除了臭名昭著的“热栈分割”问题。提供了一些新工具,包括 go 命令中对构建时源代码生成的支持。此版本还增加了对 Android 和 Native Client (NaCl) 上的 ARM 处理器以及 Plan 9 上的 AMD64 的支持。

与往常一样,Go 1.4 坚持了 兼容性承诺,并且几乎所有内容在迁移到 1.4 时都将继续编译和运行而无需更改。

语言更改

For-range 循环

在 Go 1.3 之前,for-range 循环有两种形式

for i, v := range x {
    ...
}

for i := range x {
    ...
}

如果对循环值不感兴趣,只对迭代本身感兴趣,仍然需要提及一个变量(可能是 空白标识符,如 for _ = range x 中),因为以下形式

for range x {
    ...
}

在语法上是不允许的。

这种情况看起来很尴尬,因此从 Go 1.4 开始,无变量形式现在合法。这种模式很少出现,但在出现时可以使代码更简洁。

更新:此更改严格向后兼容现有 Go 程序,但分析 Go 解析树的工具可能需要修改才能接受这种新形式,因为 RangeStmtKey 字段现在可能是 nil

在 **T 上调用方法

给定这些声明,

type T int
func (T) M() {}
var x **T

gcgccgo 都接受方法调用

x.M()

它对指向指针的指针 x 进行双重间接引用。Go 规范允许自动插入一次间接引用,但不允许两次,因此根据语言定义,此调用是错误的。因此,在 Go 1.4 中已将其禁用,这是一个重大更改,尽管受影响的程序很少。

更新:依赖于旧的、错误行为的代码将不再编译,但可以通过添加显式间接引用轻松修复。

对支持的操作系统和架构的更改

Android

Go 1.4 可以为运行 Android 操作系统的 ARM 处理器构建二进制文件。它还可以构建一个 .so 库,该库可以通过使用 mobile 子存储库中的支持包的 Android 应用程序加载。有关此实验性移植计划的简要说明,请参见 此处

ARM 上的 NaCl

先前版本引入了对 32 位 x86 (GOARCH=386) 和使用 32 位指针的 64 位 x86 (GOARCH=amd64p32) 的 Native Client (NaCl) 支持。1.4 版本增加了对 ARM (GOARCH=arm) 的 NaCl 支持。

AMD64 上的 Plan9

此版本增加了对 AMD64 处理器上 Plan 9 操作系统的支持,前提是内核支持 nsec 系统调用并使用 4K 页面。

对兼容性准则的更改

unsafe 包允许通过利用实现或数据机器表示的内部细节来破坏 Go 的类型系统。在 Go 兼容性准则 中指定的兼容性方面,从未明确指定 unsafe 的使用含义。当然,答案是我们无法保证对执行不安全操作的代码的兼容性。

我们在发布版中包含的文档中澄清了这种情况。Go 兼容性准则unsafe 包的文档现在明确指出,不安全代码不能保证保持兼容。

更新:没有技术上的更改;这只是文档的澄清。

对实现和工具的更改

对运行时的更改

在 Go 1.4 之前,运行时(垃圾回收器、并发支持、接口管理、映射、切片、字符串等)主要用 C 编写,并有一些汇编支持。在 1.4 中,大部分代码已转换为 Go,以便垃圾回收器可以扫描运行时程序的栈并获取有关哪些变量处于活动状态的准确信息。此更改很大,但对程序不应产生语义影响。

此重写允许 1.4 中的垃圾回收器完全精确,这意味着它知道程序中所有活动指针的位置。这意味着堆将更小,因为不会有任何误报导致非指针保持活动状态。其他相关的更改也减少了堆大小,与先前版本相比,整体堆大小减少了 10%-30%。

结果是栈不再分段,消除了“热栈分割”问题。当达到栈限制时,会分配一个新的更大的栈,将协程的所有活动帧复制到那里,并更新指向栈的任何指针。在某些情况下,性能可能会明显提高,并且始终更具可预测性。详细信息可在 设计文档 中找到。

使用连续栈意味着栈可以从更小的尺寸开始而不会触发性能问题,因此 1.4 中协程栈的默认起始大小已从 8192 字节减少到 2048 字节。

作为为计划在 1.5 版本中发布的并发垃圾回收器做准备,现在通过函数调用(称为写屏障)而不是直接从更新值的函数中完成对堆中指针值的写入。在下一个版本中,这将允许垃圾回收器在运行时仲裁对堆的写入。此更改对 1.4 中的程序没有语义影响,但已包含在此版本中以测试编译器和生成的性能。

接口值实现已修改。在早期版本中,接口包含一个字,该字根据存储的具体对象类型,要么是指针,要么是一个字的标量值。这种实现对垃圾回收器来说是有问题的,因此从 1.4 开始,接口值始终持有指针。在运行的程序中,大多数接口值无论如何都是指针,因此影响很小,但将整数(例如)存储在接口中的程序将看到更多的分配。

从 Go 1.3 开始,如果运行时发现内存字应包含有效指针,但实际上包含明显无效的指针(例如,值 3),则运行时会崩溃。将整数存储在指针值中的程序可能会违反此检查并崩溃。在 Go 1.4 中,设置 GODEBUG 变量 invalidptr=0 会禁用崩溃作为解决方法,但我们不能保证将来的版本能够避免崩溃;正确的解决方法是重写代码以避免整数和指针的别名。

汇编

汇编程序 cmd/5acmd/6acmd/8a 接受的语言进行了一些更改,主要是为了更容易地将类型信息传递给运行时。

首先,定义 TEXT 指令标志的 textflag.h 文件已从链接器源目录复制到标准位置,以便可以使用简单的指令包含它

#include "textflag.h"

更重要的更改在于汇编源如何定义必要的类型信息。对于大多数程序,只需将数据定义 (DATAGLOBL 指令) 从汇编移动到 Go 文件,并为每个汇编函数编写一个 Go 声明即可。汇编文档 描述了该怎么做。

更新:从其旧位置包含 textflag.h 的汇编文件仍将起作用,但应进行更新。对于类型信息,大多数汇编例程不需要更改,但都应检查。定义数据、具有非空栈帧的函数或返回指针的函数的汇编源文件需要特别注意。汇编文档 中描述了必要的(但简单的)更改。

有关这些更改的更多信息,请参见 汇编文档

gccgo 的状态

GCC 和 Go 项目的发布计划不一致。GCC 4.9 版本包含 Go 1.2 版本的 gccgo。下一个版本 GCC 5 可能会包含 Go 1.4 版本的 gccgo。

内部包

Go 的包系统使构建具有清晰边界的组件的程序变得容易,但只有两种访问形式:本地(未导出)和全局(导出)。有时希望拥有未导出的组件,例如,为了避免获取对作为公共存储库的一部分但并非旨在用于其所属程序外部的代码的接口的客户端。

Go 语言没有能力强制执行这种区别,但从 Go 1.4 开始,go 命令引入了定义“内部”包的机制,这些包可能无法被其所在源代码子树外部的包导入。

要创建这样的包,请将其放在名为 internal 的目录中或名为 internal 的目录的子目录中。当 go 命令看到导入路径中包含 internal 的包时,它会验证执行导入的包是否位于以 internal 目录的父目录为根的树中。例如,包 .../a/b/c/internal/d/e/f 只能由以 .../a/b/c 为根的目录树中的代码导入。它不能被 .../a/b/g 中或任何其他存储库中的代码导入。

对于 Go 1.4,内部包机制对主 Go 存储库强制执行;从 1.5 及更高版本开始,它将对任何存储库强制执行。

该机制的完整详细信息在 设计文档 中。

规范导入路径

代码通常存储在由公共服务(例如github.com)托管的代码库中,这意味着包的导入路径以托管服务的名称开头,例如github.com/rsc/pdf。可以使用现有的机制提供“自定义”或“虚荣”导入路径,例如rsc.io/pdf,但这会为包创建两个有效的导入路径。这是一个问题:可能会在单个程序中无意中通过两个不同的路径导入包,这是一种浪费;由于使用的路径未被识别为已过期,可能会错过对包的更新;或者通过将包移动到不同的托管服务来破坏使用旧路径的客户端。

Go 1.4 引入了一种用于 Go 源代码中包声明的注释,用于标识包的规范导入路径。如果尝试使用非规范路径进行导入,则go命令将拒绝编译导入的包。

语法很简单:在包行上添加一个识别注释。对于我们的示例,包声明将读取

package pdf // import "rsc.io/pdf"

有了这个,go命令将拒绝编译导入github.com/rsc/pdf的包,确保代码可以在不破坏用户的情况下移动。

检查是在构建时进行的,而不是在下载时进行的,因此如果go get由于此检查而失败,则错误导入的包已复制到本地机器,应手动删除。

为了补充此新功能,在更新时添加了一个检查,以验证本地包的远程存储库是否与其自定义导入的远程存储库匹配。如果远程存储库自首次下载以来已更改,则go get -u命令将无法更新包。新的-f标志覆盖此检查。

更多信息请参见设计文档

子代码库的导入路径

Go 项目子代码库(code.google.com/p/go.tools等)现在可以在自定义导入路径下使用,将code.google.com/p/go.替换为golang.org/x/,例如golang.org/x/tools。我们将在2015年6月1日前后向代码添加规范导入注释,届时Go 1.4及更高版本将停止接受旧的code.google.com路径。

更新:所有从子代码库导入的代码都应更改为使用新的golang.org路径。Go 1.0及更高版本可以解析和导入新路径,因此更新不会破坏与旧版本的兼容性。未更新的代码将在2015年6月1日前后停止使用Go 1.4编译。

go generate 子命令

go命令有一个新的子命令,go generate,用于自动运行工具在编译之前生成源代码。例如,它可以用于在.y文件上运行yacc编译器编译器以生成实现语法的Go源文件,或使用golang.org/x/tools子代码库中的新stringer工具自动生成类型常量的String方法。

有关更多信息,请参见设计文档

文件名处理的更改

构建约束,也称为构建标签,通过包含或排除文件来控制编译(请参见文档/go/build)。编译也可以通过文件本身的名称来控制,方法是在文件(.go.s扩展名之前)的扩展名后添加下划线和体系结构或操作系统的名称作为“标记”。例如,文件gopher_arm.go仅在目标处理器为ARM时才会编译。

在 Go 1.4 之前,名为arm.go的文件也以类似方式标记,但这种行为在添加新体系结构时可能会破坏源代码,导致文件突然被标记。因此,在1.4中,仅当标记(体系结构或操作系统名称)前有下划线时,文件才会以这种方式标记。

更新:依赖于旧行为的包将不再正确编译。名称类似于windows.goamd64.go的文件应添加显式的构建标签到源代码中或重命名为类似os_windows.gosupport_amd64.go的内容。

go 命令的其他更改

cmd/go命令有一些值得注意的细微更改。

包源布局的更改

在主要的 Go 源代码库中,包的源代码保存在src/pkg目录中,这很有道理,但与其他代码库(包括 Go 子代码库)不同。在 Go 1.4 中,源代码树的pkg级别现已消失,因此,例如fmt包的源代码,曾经保存在src/pkg/fmt目录中,现在位于src/fmt中上一级。

更新:像godoc这样的发现源代码的工具需要知道新位置。Go 团队维护的所有工具和服务都已更新。

SWIG

由于此版本中的运行时更改,Go 1.4 需要 SWIG 3.0.3。

其他

标准代码库的顶级misc目录过去包含 Go 对编辑器和 IDE 的支持:插件、初始化脚本等。维护这些内容变得越来越耗时,并且需要外部帮助,因为许多列出的编辑器未被核心团队成员使用。它还要求我们决定哪个插件最适合给定的编辑器,即使对于我们不使用的编辑器也是如此。

Go 社区整体更适合管理这些信息。因此,在 Go 1.4 中,此支持已从代码库中删除。相反,在wiki 页面上提供了一个精心策划的、信息丰富的可用内容列表。

性能

大多数程序在 1.4 中的运行速度与 1.3 中大致相同或略快;有些会稍微慢一些。有很多变化,因此很难准确预测会发生什么。

如上所述,大部分运行时是从 C 翻译到 Go 的,这导致堆大小有所减少。它还略微提高了性能,因为 Go 编译器在优化方面(例如内联)比用于构建运行时的 C 编译器更好。

垃圾收集器得到了加速,导致垃圾密集型程序的性能有了明显的提升。另一方面,新的写屏障再次降低了速度,通常降低的幅度大致相同,但根据它们的行为,某些程序可能会稍微慢一些或快一些。

影响性能的库更改在下面有记录。

标准库的更改

新包

此版本中没有新包。

库的主要更改

bufio.Scanner

bufio包中的Scanner类型修复了一个错误,该错误可能需要更改自定义拆分函数。该错误导致无法在 EOF 处生成空标记;该修复程序更改了拆分函数看到的结束条件。以前,如果不再有数据,扫描将在 EOF 处停止。从 1.4 开始,将在输入耗尽后在 EOF 处调用一次拆分函数,因此拆分函数可以生成最终的空标记,如文档中已承诺的那样。

更新:可能需要修改自定义拆分函数以根据需要处理 EOF 处的空标记。

syscall

syscall包现在已冻结,除非需要更改以维护核心代码库。特别是,它将不再扩展以支持核心未使用的新系统调用或不同的系统调用。原因在单独的文档中进行了详细描述。

一个新的子代码库golang.org/x/sys已创建,用作支持所有内核上的系统调用的新开发的位置。它具有更好的结构,包含三个包,每个包都保存一个UnixWindowsPlan 9系统调用的实现。这些包将得到更慷慨的管理,接受反映这些操作系统中内核接口的所有合理更改。有关更多信息,请参见文档和上面提到的文章。

更新:现有的程序不受影响,因为syscall包在 1.3 版本中基本没有变化。未来需要不在syscall包中的系统调用的开发应构建在golang.org/x/sys上。

库的次要更改

以下列表总结了库的一些细微更改,主要是新增功能。有关每个更改的更多信息,请参阅相关软件包文档。