Go 1.2 发布说明

Go 1.2 简介

自 2013 年 4 月发布 Go 1.1 版以来,发布计划已缩短,以提高发布流程的效率。此版本 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 的数量,只限制可能同时阻塞在系统调用中的数量。实际上,这个限制很难达到。

runtime/debug 包中的新函数 SetMaxThreads 控制线程计数限制。

更新:很少有函数会受到此限制的影响,但如果程序因达到限制而崩溃,可以通过调用 SetMaxThreads 来设置更高的计数来修改。更好的方法是重构程序以减少所需的线程数,从而减少内核资源的消耗。

栈大小

在 Go 1.2 中,创建 goroutine 时栈的最小大小已从 4KB 提升到 8KB。许多程序在旧大小下性能不佳,这往往会在性能关键部分引入昂贵的栈段切换。新值是通过经验测试确定的。

另一方面,runtime/debug 包中的新函数 SetMaxStack 控制单个 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 编译器套件的工作方式进行了一些语义更改。大多数用户不会受到它们的影响。

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

当程序没有 package 子句时,gc 编译器显示了其起源的遗留细节:它假定文件在 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 标志,它会导致它下载包运行的测试的依赖项,而不仅仅是包本身的依赖项。默认情况下,与以前一样,不会下载测试的依赖项。

性能

标准库中存在许多显著的性能改进;以下是其中一些。

  • compress/bzip2 解压缩速度提高约 30%。
  • crypto/des 包的速度提高了大约五倍。
  • encoding/json 包的编码速度提高了约 30%。
  • 通过在运行时中使用集成网络轮询器,Windows 和 BSD 系统上的网络性能提高了约 30%,类似于 Go 1.1 中对 Linux 和 OS X 所做的。

标准库的更改

archive/tar 和 archive/zip 包

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

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

新的 encoding 包

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

新接口名为 BinaryMarshalerBinaryUnmarshalerTextMarshalerTextUnmarshaler。完整的详细信息可在包的文档和单独的设计文档中找到。

fmt 包

fmt 包的格式化打印例程,例如 Printf,现在允许通过在格式化规范中使用索引操作以任意顺序访问要打印的数据项。无论何时要从参数列表中获取参数进行格式化,无论是作为要格式化的值还是作为宽度或规范整数,新的可选索引表示法 [n] 都将获取参数 nn 的值为 1-indexed。在此类索引操作之后,正常处理将获取的下一个参数将是 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 等的函数的程序不受影响,因为相关的函数映射将覆盖新的默认函数定义。

新包

有两个新包。

对库的微小更改

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

  • archive/zip 包添加了 DataOffset 访问器,用于返回文件中(可能已压缩的)数据在归档中的偏移量。
  • bufio 包为 ReaderWriter 添加了 Reset 方法。这些方法允许在新的输入和输出读取器和写入器上重新使用 ReadersWriters,从而节省分配开销。
  • compress/bzip2 现在可以解压缩连接的归档文件。
  • compress/flate 包在 Writer 上添加了一个 Reset 方法,以便在(例如)构建一个包含多个压缩文件的归档文件时减少分配。
  • compress/gzip 包的 Writer 类型添加了 Reset,因此可以重用它。
  • compress/zlib 包的 Writer 类型添加了 Reset,因此可以重用它。
  • container/heap 包添加了一个 Fix 方法,以提供更有效的方式更新堆中项目的位置。
  • container/list 包添加了 MoveBeforeMoveAfter 方法,它们实现了显而易见的重新排列。
  • crypto/cipher 包添加了新的 GCM 模式(伽罗瓦计数器模式),它几乎总是与 AES 加密一起使用。
  • crypto/md5 包添加了一个新的 Sum 函数,以简化哈希计算,而不会牺牲性能。
  • 同样,crypto/sha1 包添加了一个新的 Sum 函数。
  • 此外,crypto/sha256 包添加了 Sum256Sum224 函数。
  • 最后,crypto/sha512 包添加了 Sum512Sum384 函数。
  • crypto/x509 包增加了对读取和写入任意扩展的支持。
  • crypto/tls 包增加了对 TLS 1.1、1.2 和 AES-GCM 的支持。
  • database/sql 包在 DB 上添加了 SetMaxOpenConns 方法,以限制到数据库的打开连接数。
  • encoding/csv 包现在始终允许字段后有尾随逗号。
  • encoding/gob 包现在将结构的通道和函数字段视为未导出,即使它们不是。也就是说,它完全忽略它们。以前它们会触发错误,如果嵌入的结构添加了这样的字段,这可能会导致意外的兼容性问题。该包现在还支持上面描述的 encoding 包的通用 BinaryMarshalerBinaryUnmarshaler 接口。
  • encoding/json 包现在在打印字符串时总是将与号转义为“\u0026”。它现在将接受但纠正 Marshal 中的无效 UTF-8(此类输入以前被拒绝)。最后,它现在支持上面描述的 encoding 包的通用编码接口。
  • encoding/xml 包现在允许将存储在指针中的属性进行封送。它还通过新的 MarshalerUnmarshaler 以及相关的 MarshalerAttrUnmarshalerAttr 接口支持上面描述的 encoding 包的通用编码接口。该包还为 Encoder 类型添加了 Flush 方法,供自定义编码器使用。请参阅 EncodeToken 的文档以了解如何使用它。
  • flag 包现在有一个 Getter 接口,允许检索标志的值。由于 Go 1 兼容性指南,此方法无法添加到现有的 Value 接口,但所有现有的标准标志类型都实现了它。该包现在还导出了 CommandLine 标志集,其中包含命令行中的标志。
  • go/ast 包的 SliceExpr 结构体有一个新的布尔字段 Slice3,当表示具有三个索引(两个冒号)的切片表达式时,该字段设置为 true。默认值为 false,表示通常的两个索引形式。
  • go/build 包将 AllTags 字段添加到 Package 类型,以方便处理构建标签。
  • image/draw 包现在导出一个接口 Drawer,它包装了标准的 Draw 方法。Porter-Duff 运算符现在实现了此接口,实际上将操作绑定到绘图运算符,而不是显式提供。给定一个调色板图像作为其目标,新的 FloydSteinberg 实现的 Drawer 接口将使用 Floyd-Steinberg 误差扩散算法绘制图像。为了创建适合此类处理的调色板,新的 Quantizer 接口表示量化算法的实现,这些算法在给定全彩图像时选择调色板。库中没有此接口的实现。
  • image/gif 包现在可以使用新的 EncodeEncodeAll 函数创建 GIF 文件。它们的选项参数允许指定要使用的图像 Quantizer;如果为 nil,生成的 GIF 将使用新 image/color/palette 包中定义的 Plan9 颜色映射(调色板)。选项还指定要用于创建输出图像的 Drawer;如果为 nil,则使用 Floyd-Steinberg 误差扩散。
  • io 包的 Copy 方法现在对参数的优先级不同。如果一个参数实现 WriterTo 而另一个参数实现 ReaderFromCopy 现在将调用 WriterTo 来完成工作,这样通常需要更少的中间缓冲。
  • net 包默认需要 cgo,因为主机操作系统通常必须协调网络调用设置。但是,在某些系统上,可以在没有 cgo 的情况下使用网络,这样做很有用,例如避免动态链接。新的构建标签 netgo(默认关闭)允许在可能的系统上以纯 Go 方式构建 net 包。
  • net 包为 Dialer 结构体添加了一个新字段 DualStack,用于使用 RFC 6555 中描述的双 IP 栈进行 TCP 连接设置。
  • net/http 包将不再传输根据 RFC 6265 不正确的 cookie。它只会记录错误并发送空内容。此外,net/http 包的 ReadResponse 函数现在允许 *Request 参数为 nil,此时它假定为 GET 请求。最后,HTTP 服务器现在将透明地处理 HEAD 请求,无需在处理程序代码中进行特殊处理。在处理 HEAD 请求时,对 HandlerResponseWriter 的写入被 Server 吸收,并且客户端接收到空的主体,如 HTTP 规范所要求。
  • os/exec 包的 Cmd.StdinPipe 方法返回一个 io.WriteCloser,但其具体实现已从 *os.File 更改为嵌入 *os.File 的未导出类型,现在可以安全地关闭返回的值。在 Go 1.2 之前,存在一个不可避免的竞争条件,此更改修复了该问题。需要访问 *os.File 方法的代码可以使用接口类型断言,例如 wc.(interface{ Sync() error })
  • runtime 包放宽了 SetFinalizer 中 finalizer 函数的限制:实际参数现在可以是任何可赋值给函数形式类型的类型,就像 Go 中任何正常函数调用一样。
  • sort 包有一个新的 Stable 函数,它实现了稳定排序。然而,它比普通排序算法效率低。
  • strings 包添加了 IndexByte 函数,以与 bytes 包保持一致。
  • sync/atomic 包添加了一组新的交换函数,它们原子地将参数与存储在指针中的值进行交换,并返回旧值。这些函数是 SwapInt32SwapInt64SwapUint32SwapUint64SwapUintptrSwapPointer,它交换 unsafe.Pointer
  • syscall 包现在为 Darwin 实现了 Sendfile
  • testing 包现在导出了 TB 接口。它记录了与 TB 类型共同的方法,以便更容易在测试和基准测试之间共享代码。此外,AllocsPerRun 函数现在将返回值量化为整数(尽管它仍然是 float64 类型),以消除由初始化引起的任何误差,并使结果更具可重复性。
  • text/template 包现在在评估“escape”函数(如“html”)的参数时会自动解引用指针值,以使此类函数的行为与“printf”等其他打印函数保持一致。
  • time 包中,Parse 函数和 Format 方法现在处理带秒的时区偏移,例如历史日期“1871-01-01T05:33:02+00:34:08”。此外,这些例程格式中的模式匹配更加严格:非小写字母现在必须跟在“Jan”和“Mon”等标准单词之后。
  • unicode 包添加了 In,一个更易于使用但功能等同于原始 IsOneOf 的版本,用于检查字符是否是 Unicode 类别成员。