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 類別成員。