[譯]Go 1.8 新特性

astaxie發表於2016-11-11

原文 http://colobu.com/2016/11/05/golang-18-whats-coming/

譯自 tylerchr 的 What's Coming in Go 1.8

隨著Go 1.8 新特性的開發工作已經凍結,Go 1.8 將在2017年2月左右釋出,現在讓我們看一些在Go 1.8更有趣的API的改變。

HTTP server connection draining

Brad Fitzpatrick 最近關閉了一個將近四年的issue, 這個issue請求實現http.Server的連線耗盡(draining)的功能。現在可以呼叫srv.Close可以立即停止http.Server,也可以呼叫srv.Shutdown(ctx)等待已有的連線處理完畢(耗盡,draining, github.com/tylerb/graceful 的使用者應該熟悉這個特性)。

下面這個例子中,伺服器當收到SIGINT訊號後(^C)會優雅地關閉。

package main
import (
    "context"
    "io"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"
)
func main() {
    // subscribe to SIGINT signals
    stopChan := make(chan os.Signal)
    signal.Notify(stopChan, os.Interrupt)
    mux := http.NewServeMux()
    mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(5 * time.Second)
        io.WriteString(w, "Finished!")
    }))
    srv := &http.Server{Addr: ":8081", Handler: mux}
    go func() {
        // service connections
        if err := srv.ListenAndServe(); err != nil {
            log.Printf("listen: %s\n", err)
        }
    }()
    <-stopChan // wait for SIGINT
    log.Println("Shutting down server...")
    // shut down gracefully, but wait no longer than 5 seconds before halting
    ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
    srv.Shutdown(ctx)
    log.Println("Server gracefully stopped")
}

一旦收到SIGINT訊號,伺服器會立即停止接受新的連線,srv.ListenAndServe()會返回http.ErrServerClosedsrv.Shutdown會一直阻塞,直到所有未完成的request都被處理完以及它們底層的連線被關閉。

更復雜的處理可以通過context實現,例如使用context.Timeout實現最大的關閉等待時間。你可以嘗試複製 https://github.com/tylerchr/examples/tree/master/draining 中的例子並實現它。

通過 http.Pusher 實現 HTTP/2.0 server push

HTTP/2.0 包含 Server Push 特性, 允許 HTTP/2 伺服器主動地傳送額外的 HTTP response 給客戶端,即使客戶端沒有傳送請求。目標是在客戶端無需請求的情況下,伺服器可以及時地將客戶端所需的資源推送給客戶端。可以檢視wiki HTTP/2 Server Push看具體的例子。

如果一個伺服器支援 HTTP/2, 提供給 handlerhttp.ResponseWriter 會實現 http.Pusher 介面。Handler 可以使用這個功能區觸發Server Push, 虛擬的請求(synthetic request)可以被註冊的 http.Server Handler所處理。

下面的程式處理/index.html, 可以push一個/static/gopher.png:

package main
import "net/http"
func main() {
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
    http.Handle("/index.html", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // server push is available if w implements http.Pusher
        if p, ok := w.(http.Pusher); ok {
            p.Push("/static/gopher.png", nil}
        }
        // load the main page
        w.Header().Set("Content-Type", "text/html")
        w.Write([]byte(`<img src="/static/gopher.png" />`))
    }))
    http.ListenAndServeTLS(":4430", "cert.pem", "key.pem", nil)
}

你可以從 https://github.com/tylerchr/examples/serverpush 克隆這個例子,下面是在 Chrome 54 訪問的結果:

明顯地可以在 Initiator 那一列中看到 gopher.png被 Push,你也可以看到標藍色的gopher.png先於/index.html被接收過來,這表明這個資源先於請求之前被推送到客戶端。HTML下載完後[譯]Go 1.8 新特性可以顯示。

有人可能會問如何寫一個測試用來校驗實現 Server Push的 Handler。因為http.NewTLSServer沒有啟動 HTTP/2 伺服器,httptest.ResponseRecorder也沒有實現http.Pusher。我的解決方案是包裝httptest.ResponseRecorder實現Pusher介面,這裡有個例子。

database/sql

database/sql包有幾個主要的改變,可以讓使用者更好的控制資料庫查詢,允許使用者更好的利用資料庫的特性。

  • 查詢可以使用context.Context取消查詢
  • 純資料庫列型別可以通過sql.ColumnType得到
  • 如果底層資料庫支援,查詢可以使用命名引數

更多的細節可以閱讀Daniel Theophanes的文章 What is new in database/sql?,他實現了大部分的改變。

plugin包實現動態外掛

新增加的標準庫plugin提供了初步的外掛支援,它允許程式可以在執行的時候動態的載入外掛。

但是這個庫看起來還是bug多多,我甚至不能寫一個正常的程式來測試它,但是假定它的使用應該如下面的樣子:

// hexify.go
package main
import "encoding/hex"
func Hexify(in string) string {
    return hex.EncodeToString([]byte(in))
}
$ go build -buildmode=shared hexify.go
// produces hexify.so
// main.go
package main
import "plugin"
func main() {
    p, _ = plugin.Open("hexify.so")
    f := p.Lookup("Hexify")
    fmt.Println(f.(func(string) string)("gopher"))
    // 676f70686572
}

在這個例子中,hexify.go實現了Hexify函式,它被編譯成一個共享庫,第二個程式動態載入它。這允許Go程式可以不在編譯的時候也能呼叫其它的庫。

別名

別名(aliasing)曾被增加到 Go 1.8 的語言規範中,但是現在又被移除了,看這個說明: this post from Russ Cox,有可能會出現在 Go 1.9中。 這個特性也引起了很多的爭議,

指示符別名(Identifier aliasing)用來定義多個型別為同一個型別的語法。一個用處用來重構複雜的程式碼的時候,允許重新劃分包而不必帶來型別的不一致。 Ian Lance Taylor舉了一個例子: 舉個具體的例子,將擴充套件包golang.org/x/net/context移動到標準庫context的過程。因為context已經被廣泛地使用,將所有的使用者的程式碼統一轉換很困難,因此允許這兩個包通用很有必要。

別名的定義如下:

type Foo => pkg.Bar

這個語法定義 Foo 是 pkg.Bar別名。Foo可以用在任何pkg.Bar出現的地方。以上個例子為例,任何需要型別 golang.org/x/net/context 的地方都可以用標準庫context代替,它們是等價的。

別名也可以用在常量、變數、函式等型別上。

這是一個很有爭議的特性,可以參考issue 16339golang-dev post 看大家的討論。因為它從Go 1.8中移除了,大家可以暫時不用關注這個特性了。

新的slice排序API

統一的slice排序由新的 sort.Slice 函式實現。它允許任意的slice都可以被排序,只需提供一個回撥比較函式即可,而不是像以前要提供一個特定的sort.Interface的實現。這個函式沒有返回值。想其它的排序函式一樣,它提供了原地的排序。

下面的例子根據海拔高度排序知名山峰的slice。

type Peak struct {
    Name      string
    Elevation int // in feet
}
peaks := []Peak{
    {"Aconcagua", 22838},
    {"Denali", 20322},
    {"Kilimanjaro", 19341},
    {"Mount Elbrus", 18510},
    {"Mount Everest", 29029},
    {"Mount Kosciuszko", 7310},
    {"Mount Vinson", 16050},
    {"Puncak Jaya", 16024},
}
// does an in-place sort on the peaks slice, with tallest peak first
sort.Slice(peaks, func(i, j int) bool {
    return peaks[i].Elevation >= peaks[j].Elevation
})
// peaks is now sorted

通過sort.Interface型別的Len()Swap(i, j int)提供了抽象的排序型別,這是以前的排序方法,而Less(i, j int)作為一個比較回撥函式,可以簡單地傳遞給sort.Slice進行排序。

其它

87b1aaa encoding/base64 encoder現在有了嚴格模式. 6ba5b32 expvar暴露出來,可以用在其它的mux中. 003a598 偽隨機碼可以通過rand.Uint64()產生 (先前僅支援uint32). 67ea710 增加了一個新的time.Until函式,和time.Since對應. net/http故意只實現了使用TLS的HTTP/2,你可以檢視[issue 14141]https://github.com/golang/go/issues/14141()瞭解細節sort.SliceStable提供了穩定的slice排序,就像以前的sort.Stable一樣。

譯者增加的內容

Go 1.8 一個很大的特性就是效能的提升,包括二進位制檔案的大小、編譯速度和執行速度。 並且非常大的提升就是提供小於100us GC暫停。

net/http提供了更多的超時設定,比如ReadHeaderTimeoutIdleTimeout

一個完整的改動列表:Go 1.8

相關文章