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 解析树的工具可能需要修改才能接受这种新形式,因为 RangeStmt
的 Key
字段现在可能是 nil
。
在 **T 上调用方法
给定这些声明,
type T int
func (T) M() {}
var x **T
gc
和 gccgo
都接受方法调用
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/5a
、cmd/6a
和 cmd/8a
接受的语言进行了一些更改,主要是为了更容易地将类型信息传递给运行时。
首先,定义 TEXT
指令标志的 textflag.h
文件已从链接器源目录复制到标准位置,以便可以使用简单的指令包含它
#include "textflag.h"
更重要的更改在于汇编源如何定义必要的类型信息。对于大多数程序,只需将数据定义 (DATA
和 GLOBL
指令) 从汇编移动到 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.go
或amd64.go
的文件应添加显式的构建标签到源代码中或重命名为类似os_windows.go
或support_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
目录过去包含 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已创建,用作支持所有内核上的系统调用的新开发的位置。它具有更好的结构,包含三个包,每个包都保存一个Unix、Windows和Plan 9系统调用的实现。这些包将得到更慷慨的管理,接受反映这些操作系统中内核接口的所有合理更改。有关更多信息,请参见文档和上面提到的文章。
更新:现有的程序不受影响,因为syscall
包在 1.3 版本中基本没有变化。未来需要不在syscall
包中的系统调用的开发应构建在golang.org/x/sys
上。
库的次要更改
以下列表总结了库的一些细微更改,主要是新增功能。有关每个更改的更多信息,请参阅相关软件包文档。
archive/zip
包的Writer
现在支持Flush
方法。compress/flate
、compress/gzip
和compress/zlib
包现在支持解压缩器的Reset
方法,允许它们重用缓冲区并提高性能。compress/gzip
包还具有Multistream
方法来控制对多流文件的支持。crypto
包现在有一个Signer
接口,由crypto/ecdsa
和crypto/rsa
中的PrivateKey
类型实现。crypto/tls
包现在支持 RFC 7301 中定义的 ALPN。crypto/tls
包现在支持通过Config
结构的新CertificateForName
函数以编程方式选择服务器证书。- 同样在 crypto/tls 包中,服务器现在支持 TLS_FALLBACK_SCSV 以帮助客户端检测回退攻击。(Go 客户端根本不支持回退,因此不会受到这些攻击的影响。)
database/sql
包现在可以列出所有已注册的Drivers
。debug/dwarf
包现在支持UnspecifiedType
。- 在
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
实现(如RGBA
和Gray
)除了通用At
方法外,还具有专门的RGBAAt
和GrayAt
方法。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 中错过的非常小的分配。由于答案更准确,这可能会破坏使用ReadMemStats
或AllocsPerRun
的测试。- 在
runtime
包中,一个数组PauseEnd
已添加到MemStats
和GCStats
结构中。此数组是垃圾回收暂停结束时间的循环缓冲区。相应的暂停持续时间已记录在PauseNs
中。 runtime/race
包现在支持 FreeBSD,这意味着go
命令的-race
标志现在可以在 FreeBSD 上使用。sync/atomic
包有一个新类型Value
。Value
提供了一种有效的机制,用于对任意类型的值的原子加载和存储。- 在
syscall
包在 Linux 上的实现中,Setuid
和Setgid
已被禁用,因为这些系统调用在调用线程上运行,而不是在整个进程上运行,这与其他平台不同,也不是预期结果。 testing
包有一个新功能,可以更有效地控制一组测试的运行。如果测试代码包含一个函数func TestMain(m *
则将调用该函数而不是直接运行测试。testing.M
)M
结构包含用于访问和运行测试的方法。- 同样在
testing
包中,一个新的Coverage
函数报告当前的测试覆盖率分数,使各个测试能够报告它们对整体覆盖率的贡献程度。 text/scanner
包的Scanner
类型有一个新函数IsIdentRune
,允许在扫描时控制标识符的定义。text/template
包的布尔函数eq
、lt
等已泛化,以允许比较有符号和无符号整数,从而简化了它们的实际使用。(以前只能比较相同符号的值。)所有负值都比所有无符号值小。time
包现在使用微前缀的标准符号,即微符号(U+00B5 ‘µ’),来打印微秒持续时间。ParseDuration
仍然接受us
,但该包不再将微秒打印为us
。
更新:依赖于持续时间输出格式但未使用 ParseDuration 的代码需要更新。