Go 1.2 版本发布说明

Go 1.2 简介

自从 Go 1.1 版本 于 2013 年 4 月发布以来,发布周期已缩短,以提高发布流程的效率。此版本,即 Go 1.2 版本(简称 Go 1.2),大约在 1.1 版本发布六个月后发布,而 1.1 版本在 1.0 版本发布后则花费了一年多的时间才发布。由于时间周期缩短,1.2 版本的变化幅度小于从 1.0 到 1.1 版本的变化幅度,但它仍然包含一些重要的改进,包括一个更好的调度程序和一个新的语言特性。当然,Go 1.2 保持了 兼容性承诺。绝大多数使用 Go 1.1(或 1.0)构建的程序在迁移到 1.2 后无需任何更改即可运行,尽管对语言的一个角落引入了一个限制可能会暴露已存在的错误代码(请参阅关于 nil 的使用 的讨论)。

语言更改

为了明确规范,已经澄清了一个极端情况,这对程序有影响。此外,还增加了一个新的语言特性。

nil 的使用

现在语言规定,出于安全原因,某些 nil 指针的使用将保证触发运行时 panic。例如,在 Go 1.0 中,给定如下代码:

type T struct {
    X [1<<24]byte
    Field int32
}

func main() {
    var x *T
    ...
}

nil 指针 x 可以被用来错误地访问内存:表达式 x.Field 可以访问地址 1<<24 处的内存。为了防止这种不安全的行为,在 Go 1.2 中,编译器现在保证任何通过 nil 指针进行的间接访问(如这里所示,也包括对数组的 nil 指针、nil 接口值、nil 切片等)要么 panic,要么返回一个正确、安全的非 nil 值。简而言之,任何显式或隐式需要计算 nil 地址的表达式都是错误的。实现可以在编译后的程序中注入额外的测试以强制执行此行为。

更多详细信息请参阅 设计文档

更新:大多数依赖于旧行为的代码都是错误的,并在运行时失败。此类程序需要手动更新。

三索引切片

Go 1.2 添加了在对现有数组或切片使用切片操作时指定容量和长度的功能。切片操作通过描述已创建的数组或切片的一个连续部分来创建一个新的切片。

var array [10]int
slice := array[2:4]

切片的容量是切片可以容纳的最大元素数量,即使在重新切片后也是如此;它反映了底层数组的大小。在此示例中,slice 变量的容量为 8。

Go 1.2 添加了新的语法,允许切片操作指定容量和长度。第二个冒号引入容量值,该值必须小于或等于源切片或数组的容量,并根据起始位置进行调整。例如,

slice = array[2:4:7]

将切片的长度设置为与前面示例中相同,但其容量现在仅为 5 个元素(7-2)。无法使用此新切片值访问原始数组的最后三个元素。

在此三索引表示法中,缺少第一个索引([:i:j])默认为零,但其他两个索引必须始终显式指定。Go 的未来版本可能会为这些索引引入默认值。

更多详细信息请参阅 设计文档

更新:这是一个向后兼容的更改,不影响任何现有程序。

实现和工具的更改

调度程序中的抢占

在之前的版本中,无限循环的 goroutine 可能会使同一线程上的其他 goroutine 饿死,当 GOMAXPROCS 只提供一个用户线程时,这是一个严重的问题。在 Go 1.2 中,这个问题得到了部分解决:调度程序在进入函数时偶尔会被调用。这意味着任何包含(非内联)函数调用的循环都可以被抢占,从而允许其他 goroutine 在同一线程上运行。

线程数量限制

Go 1.2 引入了对单个程序在其地址空间中可以拥有的线程总数的可配置限制(默认值为 10,000),以避免某些环境中的资源饥饿问题。请注意,goroutine 会多路复用到线程上,因此此限制不会直接限制 goroutine 的数量,而只会限制可能同时阻塞在系统调用中的数量。在实践中,很难达到此限制。

SetMaxThreads 函数(位于 runtime/debug 包中)控制线程计数限制。

更新:很少有函数会受到此限制的影响,但如果程序由于达到此限制而死亡,则可以修改它以调用 SetMaxThreads 来设置更高的计数。更好的方法是重构程序以减少线程需求,从而减少对内核资源的消耗。

栈大小

在 Go 1.2 中,创建 goroutine 时栈的最小大小已从 4KB 提高到 8KB。许多程序在旧大小下遇到了性能问题,旧大小往往会在性能关键部分引入代价高昂的栈段切换。新数字是通过经验测试确定的。

另一方面,SetMaxStack 函数(位于 runtime/debug 包中)控制单个 goroutine 栈的最大大小。64 位系统上的默认值为 1GB,32 位系统上的默认值为 250MB。在 Go 1.2 之前,失控的递归很容易消耗机器上的所有内存。

更新:增加的最小栈大小可能会导致具有许多 goroutine 的程序使用更多内存。没有解决方法,但未来版本的计划包括新的栈管理技术,应该可以更好地解决此问题。

Cgo 和 C++

cgo 命令现在将调用 C++ 编译器来构建链接到的库中用 C++ 编写的任何部分;文档 中提供了更多详细信息。

Godoc 和 vet 移至 go.tools 子存储库

这两个二进制文件仍然包含在发行版中,但 godoc 和 vet 命令的源代码已移至 go.tools 子存储库。

此外,godoc 程序的核心已被拆分为一个 ,而命令本身则位于一个单独的 目录 中。此移动允许轻松更新代码,并将代码分离为库和命令,从而更容易为本地站点和不同的部署方法构建自定义二进制文件。

更新:由于 godoc 和 vet 不是库的一部分,因此没有客户端 Go 代码依赖于它们的源代码,因此不需要更新。

golang.org 提供的二进制发行版包含这些二进制文件,因此使用这些发行版的用户不受影响。

从源代码构建时,用户必须使用“go get”安装 godoc 和 vet。(二进制文件将继续安装在其通常的位置,而不是 $GOPATH/bin 中。)

$ go get code.google.com/p/go.tools/cmd/godoc
$ go get code.google.com/p/go.tools/cmd/vet

gccgo 的状态

我们预计未来的 GCC 4.9 版本将包含对 Go 1.2 提供完全支持的 gccgo。在当前(4.8.2)版本的 GCC 中,gccgo 实现 Go 1.1.2。

gc 编译器和链接器的更改

Go 1.2 对 gc 编译器套件的工作原理进行了一些语义更改。大多数用户不会受到这些更改的影响。

cgo 命令现在可以在库中包含 C++ 时工作。有关详细信息,请参阅 cgo 文档。

当程序没有 package 子句时,gc 编译器会显示其起源的一个残留细节:它假设文件位于 package main 中。过去已被抹去,现在缺少 package 子句将是一个错误。

在 ARM 上,工具链支持“外部链接”,这是能够使用 gc 工具链构建共享库以及为需要动态链接的环境提供动态链接支持的一步。

在 ARM 的运行时中,使用 5a 时,可以使用 R9R10 直接引用运行时内部的 m(机器)和 g(goroutine)变量。现在必须使用其正确的名称来引用它们。

同样在 ARM 上,5l 链接器(原文如此)现在将 MOVBSMOVHS 指令定义为 MOVBMOVH 的同义词,以更清楚地区分带符号和无符号的子字移动;无符号版本已经存在,并带有 U 后缀。

测试覆盖率

go test 的一个主要新特性是它现在可以计算并(在新的、单独安装的“go tool cover”程序的帮助下)显示测试覆盖率结果。

cover 工具是 go.tools 子存储库的一部分。可以通过运行以下命令安装它:

$ go get code.google.com/p/go.tools/cmd/cover

cover 工具执行两项操作。首先,当“go test”给出 -cover 标志时,它会自动运行以重写包的源代码并插入检测语句。然后,测试将像往常一样编译和运行,并报告基本的覆盖率统计信息。

$ go test -cover fmt
ok      fmt 0.060s  coverage: 91.4% of statements
$

其次,对于更详细的报告,“go test”的不同标志可以创建一个覆盖率配置文件,然后 cover 程序(使用“go tool cover”调用)可以分析该文件。

有关如何生成和分析覆盖率统计信息的详细信息,可以通过运行以下命令找到

$ go help testflag
$ go tool cover -help

go doc 命令已删除

“go doc” 命令已删除。请注意,godoc 工具本身并没有被删除,只是 go 命令对其的封装被删除了。它所做的只是根据包路径显示包的文档,而 godoc 本身已经能够以更高的灵活性做到这一点。因此,为了减少文档工具的数量,并且作为 godoc 重构的一部分,鼓励将来使用更好的选项,该命令已被删除。

更新:对于仍然需要运行以下命令的精确功能的用户

$ go doc

在目录中,其行为与运行以下命令相同

$ godoc .

go 命令的更改

go get 命令现在有一个 -t 标志,它会导致该命令下载包运行测试的依赖项,而不仅仅是包本身的依赖项。默认情况下,与之前一样,不会下载测试的依赖项。

性能

标准库中有一些重大的性能改进;以下是一些示例。

标准库的更改

archive/tar 和 archive/zip 包

archive/tararchive/zip 包对其语义进行了更改,这可能会破坏现有的程序。问题在于它们都提供了 os.FileInfo 接口的实现,但该实现不符合该接口的规范。特别是,它们的 Name 方法返回条目的完整路径名,但接口规范要求该方法仅返回基本名称(最后一个路径元素)。

更新:由于此行为是新实现的,并且有点模糊,因此可能没有代码依赖于此错误的行为。如果确实有程序依赖于此行为,则需要手动识别并修复它们。

新的 encoding 包

有一个新的包,encoding,它定义了一组标准的编码接口,可用于为诸如 encoding/xmlencoding/jsonencoding/binary 等包构建自定义编组器和解编组器。这些新的接口已被用于整理标准库中的一些实现。

这些新接口称为 BinaryMarshalerBinaryUnmarshalerTextMarshalerTextUnmarshaler。完整详细信息请参阅该包的 文档 和单独的 设计文档

fmt 包

fmt 包的格式化打印例程(例如 Printf)现在允许通过在格式化规范中使用索引操作来以任意顺序访问要打印的数据项。无论何时要从参数列表中获取参数以进行格式化(作为要格式化的值或作为宽度或规范整数),新的可选索引表示法 [n] 将获取参数 nn 的值从 1 开始。在进行此类索引操作后,通过正常处理要获取的下一个参数将是 n+1。

例如,正常的 Printf 调用

fmt.Sprintf("%c %c %c\n", 'a', 'b', 'c')

将创建字符串 "a b c",但是使用以下索引操作

fmt.Sprintf("%[3]c %[1]c %c\n", 'a', 'b', 'c')

结果为“"c a b"[3] 索引访问第三个格式化参数,即 'c'[1] 访问第一个,'a',然后下一个获取访问其后的参数,'b'

此功能的动机是可编程格式语句,用于以不同的顺序访问参数以进行本地化,但它还有其他用途

log.Printf("trace: value %v of type %[1]T\n", expensiveFunction(a.b[c]))

更新:格式规范语法的更改严格向后兼容,因此它不会影响任何正在运行的程序。

text/template 和 html/template 包

text/template 包在 Go 1.2 中有几个更改,这两个更改也反映在 html/template 包中。

首先,有一些用于比较基本类型的新默认函数。这些函数列在以下表格中,该表格显示了它们的名字和相关的常用比较运算符。

名称 运算符
eq ==
ne !=
lt <
le <=
gt >
ge >=

这些函数的行为与相应的 Go 运算符略有不同。首先,它们仅对基本类型(boolintfloat64string 等)进行操作。(Go 还允许在某些情况下比较数组和结构体。)其次,只要值是相同类型的值,就可以比较它们:例如,任何带符号整数的值都可以与任何其他带符号整数的值进行比较。(Go 不允许比较 int8int16)。最后,eq 函数(仅)允许将第一个参数与一个或多个后续参数进行比较。此示例中的模板

{{if eq .A 1 2 3}} equal {{else}} not equal {{end}}

如果 .A 等于 1、2 或 3 中的任何一个,则报告“equal”。

第二个更改是语法的一小部分添加,使“if else if”链更容易编写。无需编写

{{if eq .A 1}} X {{else}} {{if eq .A 2}} Y {{end}} {{end}}

可以将第二个“if”折叠到“else”中,并且只有一个“end”,如下所示

{{if eq .A 1}} X {{else if eq .A 2}} Y {{end}}

这两种形式的效果相同;区别仅在于语法。

更新:“else if”更改和比较函数都不会影响现有程序。那些已经通过函数映射定义了名为 eq 等函数的程序不受影响,因为关联的函数映射将覆盖新的默认函数定义。

新的包

有两个新的包。

库的次要更改

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