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 中已禁止此调用,这是一项破坏性更改,尽管受影响的程序很少。

更新:依赖旧的错误行为的代码将不再编译,但通过添加显式解引用很容易修复。

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

安卓

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%。

结果是堆栈不再分段,消除了“热拆分”问题。当达到堆栈限制时,会分配一个新的更大的堆栈,所有活动的 goroutine 帧都被复制到那里,并且对堆栈的任何指针都会更新。在某些情况下,性能可能会明显更好,并且总是更可预测。详细信息可在设计文档中找到。

使用连续堆栈意味着堆栈可以更小,而不会引发性能问题,因此 1.4 中 goroutine 堆栈的默认起始大小已从 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 版本包含 gccgo 的 Go 1.2 版本。下一个版本 GCC 5 很可能将包含 gccgo 的 Go 1.4 版本。

内部包

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 命令有一些值得注意的次要更改。

  • 除非使用 cgo 构建包,否则 go 命令现在拒绝编译 C 源文件,因为相关的 C 编译器(6c 等)计划在未来版本中从安装中移除。(它们现在仅用于构建运行时的一部分。)在任何情况下,正确使用它们都很困难,因此任何现有的使用都可能不正确,因此我们已禁用它们。
  • go test 子命令有一个新的标志 -o,用于设置生成二进制文件的名称,对应于其他子命令中的相同标志。非功能性 -file 标志已移除。
  • go test 子命令将编译和链接包中所有 *_test.go 文件,即使它们中没有 Test 函数。它以前会忽略此类文件。
  • 对于非开发安装,go build 子命令的 -a 标志的行为已更改。对于运行已发布发行版的安装,-a 标志将不再重新构建标准库和命令,以避免覆盖安装的文件。

包源布局的更改

在主要的 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 目录曾经包含对编辑器和 IDE 的 Go 支持:插件、初始化脚本等。维护这些变得耗时且需要外部帮助,因为许多列出的编辑器未被核心团队成员使用。它还需要我们决定哪个插件最适合给定编辑器,即使是我们不使用的编辑器。

Go 社区总体上更适合管理这些信息。因此,在 Go 1.4 中,此支持已从存储库中移除。取而代之的是,在维基页面上有一个精选的、信息丰富的可用列表。

性能

大多数程序在 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

对库的微小更改

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

  • archive/zip 包的 Writer 现在支持 Flush 方法。
  • compress/flatecompress/gzipcompress/zlib 包现在支持解压缩器的 Reset 方法,允许它们重用缓冲区并提高性能。compress/gzip 包还具有 Multistream 方法来控制对多流文件的支持。
  • crypto 包现在有一个 Signer 接口,由 crypto/ecdsacrypto/rsa 中的 PrivateKey 类型实现。
  • crypto/tls 包现在支持 RFC 7301 中定义的 ALPN。
  • crypto/tls 包现在通过 Config 结构体的新 CertificateForName 函数支持服务器证书的编程选择。
  • 同样在 crypto/tls 包中,服务器现在支持 TLS_FALLBACK_SCSV 以帮助客户端检测回退攻击。(Go 客户端根本不支持回退,因此它不受这些攻击的影响。)
  • database/sql 包现在可以列出所有注册的 Drivers
  • debug/dwarf 包现在支持 UnspecifiedTypes。
  • encoding/asn1 包中,具有默认值的可选元素现在仅在其具有该值时才被省略。
  • encoding/csv 包不再引用空字符串,但引用数据结束标记 \.(反斜杠点)。这由 CSV 定义允许,并使其与 Postgres 更好地协作。
  • encoding/gob 包已重写,以消除对不安全操作的使用,使其可在不允许使用 unsafe 包的环境中使用。对于典型用途,它将慢 10-30%,但增量取决于数据类型,在某些情况下,尤其涉及数组时,它可能会更快。没有功能上的变化。
  • encoding/xml 包的 Decoder 现在可以报告其输入偏移量。
  • fmt 包中,指向映射的格式化已更改,以与指向结构、数组等的格式化保持一致。例如,&map[string]int{"one": 1} 现在默认打印为 &map[one: 1],而不是十六进制指针值。
  • image 包的 Image 实现,例如 RGBAGray,除了通用 At 方法外,还具有专门的 RGBAAtGrayAt 方法。
  • image/png 包现在有一个 Encoder 类型来控制用于编码的压缩级别。
  • math 包现在有一个 Nextafter32 函数。
  • net/http 包的 Request 类型有一个新的 BasicAuth 方法,该方法从使用 HTTP 基本身份验证方案的已验证请求返回用户名和密码。
  • net/http 包的 Transport 类型有一个新的 DialTLS 钩子,允许自定义出站 TLS 连接的行为。
  • net/http/httputil 包的 ReverseProxy 类型有一个新字段 ErrorLog,它提供用户对日志记录的控制。
  • os 包现在通过 Symlink 函数在 Windows 操作系统上实现了符号链接。其他操作系统已经具有此功能。还有一个新的 Unsetenv 函数。
  • reflect 包的 Type 接口有一个新方法 Comparable,该方法报告类型是否实现通用比较。
  • 同样在 reflect 包中,由于运行时接口实现的更改,Value 接口现在是三个字而不是四个字。这节省了内存,但没有语义影响。
  • runtime 包现在在 Windows 上实现了单调时钟,就像它在其他系统上一样。
  • runtime 包的 Mallocs 计数器现在计算 Go 1.3 中遗漏的非常小的分配。这可能会由于更准确的结果而破坏使用 ReadMemStatsAllocsPerRun 的测试。
  • runtime 包中,一个数组 PauseEnd 已添加到 MemStatsGCStats 结构体中。此数组是一个循环缓冲区,记录垃圾回收暂停结束的时间。相应的暂停持续时间已记录在 PauseNs
  • runtime/race 包现在支持 FreeBSD,这意味着 go 命令的 -race 标志现在在 FreeBSD 上有效。
  • sync/atomic 包有一个新类型 ValueValue 为任意类型值的原子加载和存储提供了一种高效的机制。
  • syscall 包在 Linux 上的实现中,Setuidtesting 包有一个新功能,可以更好地控制一组测试的运行。如果测试代码包含一个函数
    func TestMain(m *testing.M)
    
    该函数将被调用,而不是直接运行测试。M 结构包含访问和运行测试的方法。
  • 同样在 testing 包中,一个新的 Coverage 函数报告当前的测试覆盖率分数,使单个测试能够报告它们对整体覆盖率的贡献程度。
  • text/scanner 包的 Scanner 类型有一个新函数 IsIdentRune,允许在扫描时控制标识符的定义。
  • text/template 包的布尔函数 eqlt 等已通用化,允许比较有符号和无符号整数,从而简化了它们在实践中的使用。(以前只能比较具有相同符号性的值。)所有负值都小于所有无符号值。
  • time 包现在使用微前缀的标准符号,即微符号(U+00B5 'µ'),来打印微秒持续时间。ParseDuration 仍然接受 us,但该包不再将微秒打印为 us
    更新:依赖持续时间输出格式但未使用 ParseDuration 的代码需要更新。