Go 1.2 發行說明

Go 1.2 簡介

自 2013 年 4 月 Go 1.1 版 發布以來,發布時程已縮短,以提高發布程序的效率。這個版本,簡稱 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 指標的方式保證會觸發執行時間驚恐。例如,在 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 序列等,都會驚恐或傳回正確的非 nil 安全值。簡而言之,任何明示或暗示需要評估 nil 位址的表達式都是錯誤。此實作可能會在編譯程式中注入額外的測試來強制此行為。

詳細資料請見 設計文件

更新:大多數仰賴舊行為的程式碼都是有錯誤的,執行時會失敗。此類程式需要手動更新。

三索引序列

Go 1.2 新增了使用既有陣列或序列的切片運算時,除了長度之外還可指定容量的功能。切片運算會建立一個新序列,方法是描述已建立陣列或序列的連續區段

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

序列的容量是切片可容納的最大元素數目,即使重新切片之後仍然是這樣,它反映了底層陣列的大小。在此範例中,切片變數的容量為 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。許多程式在效能重要的區段引入了昂貴的堆疊區段切換而造成效能問題。新的數字是依據經驗測試而定。

在另一個極端,新的函式 SetMaxStackruntime/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 文件。

gc 編譯器在程式沒有 package 子句時,顯示其原始設定的殘留詳細資料:它假設檔案在 package main 中。過去已消除,現在遺失 package 子句是錯誤。

在 ARM 上,工具鏈支援「外部連結」,這有助於使用 gc 工具鏈建置共用函式庫,以及為需要支援動態連結的環境提供動態連結支援。

在針對 ARM 的執行時期中,使用 5a 時,通常可使用 R9R10 直接參照執行時期內部的 m(機器)和 g(goroutine)變數。現在需用適當的名稱來參照這些變數。

同樣也在 ARM 上,5l 連結器 (sic) 現將 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 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/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 中的 任何一個,則報告「相等」。

第二個變更為語法加入一個小外掛,讓「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 等函數的則不受影響,因為相關函數對應將會覆寫新的預設函數定義。

新套件

新增了兩個套件。

函式庫的次要變更

以下清單總計許多函式庫的次要變更,大部分為外掛。請見相關套件文件,以取得每個變更的更多資訊。