Go 1.8中值得關注的幾個變化
在已經過去的2016年,Go語言繼在2009年之后再次成為編程語言界的明星- 問鼎 TIOBE 2016年度語言。這與Go team、Go community和全世界的Gophers的努力是分不開的。按計劃在這個2月份,Go team將正式發布Go 1.8版本(截至目前,Go的最新版本是 Go 1.8rc3 )。在這里我們一起來看一下在Go 1.8版本中都有哪些值得Gopher們關注的變化。
一、語言(Language)
Go 1.8版本依舊堅守Go Team之前的承諾,即 Go1兼容性 :使用Go 1.7及以前版本編寫的Go代碼,理論上都可以通過Go 1.8進行編譯并運行。因此在臆想中的 Go 2.0 變成現實之前,每個Go Release版本在語言這方面的“改變”都會是十分微小的。
1、僅tags不同的兩個struct可以相互做顯式類型轉換
在Go 1.8版本以前,兩個struct即便字段個數相同且每個字段類型均一樣,但如果某個字段的tag描述不一樣,這兩個struct相互間也不能做顯式類型轉換,比如:
//go18-examples/language/structtag.go
package main
import "fmt"
type XmlEventRegRequest struct {
AppID string `xml:"appid"`
NeedReply int `xml:"Reply,omitempty"`
}
type JsonEventRegRequest struct {
AppID string `json:"appid"`
NeedReply int `json:"reply,omitempty"`
}
func convert(in *XmlEventRegRequest) *JsonEventRegRequest {
out := &JsonEventRegRequest{}
*out = (JsonEventRegRequest)(*in)
return out
}
func main() {
in := XmlEventRegRequest{
AppID: "wx12345678",
NeedReply: 1,
}
out := convert(∈)
fmt.Println(out)
}
采用Go 1.7.4版本go compiler進行編譯,我們會得到如下錯誤輸出:
$go build structtag.go
# command-line-arguments
./structtag.go:17: cannot convert *in (type XmlEventRegRequest) to type JsonEventRegRequest
但在Go 1.8中,gc將忽略tag值的不同,使得顯式類型轉換成為可能:
$go run structtag.go
&{wx12345678 1}
改變雖小,但帶來的便利卻不小,否則針對上面代碼中的convert,我們只能做逐一字段賦值了。
2、浮點常量的指數部分至少支持16bits長
在Go 1.8版本之前的 The Go Programming Language Specificaton 中,關于浮點數常量的指數部分的描述如下:
Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed exponent of at least 32 bits.
在Go 1.8版本中,文檔中對于浮點數常量指數部分的長度的實現的條件放寬了,由支持最少32bit,放寬到最少支持16bits:
Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed binary exponent of at least 16 bits.
但Go 1.8版本go compiler實際仍然支持至少32bits的指數部分長度,因此這個改變對現存的所有Go源碼不會造成影響。
二、標準庫(Standard Library)
Go號稱是一門”Batteries Included”編程語言。“Batteries Included”指的就是Go語言強大的標準庫。使用Go標準庫,你可以完成絕大部分你想要的功能,而無需再使用第三方庫。Go語言的每次版本更新,都會在標準庫環節增加強大的功能、提升性能或是提高使用上的便利性。每次版本更新,標準庫也是改動最大的部分。這次也不例外,我們逐一來看。
1、便于slice sort的sort.Slice函數
在Go 1.8之前我們要對一個slice進行sort,需要定義出實現了下面接口的slice type:
//$GOROOT/src/sort.go
... ...
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
標準庫定義了一些應對常見類型slice的sort類型以及對應的函數:
StringSlice -> sort.Strings
IntSlice -> sort.Ints
Float64Slice -> sort.Float64s
但即便如此,對于用戶定義的struct或其他自定義類型的slice進行排序仍需定義一個新type,比如下面這個例子中的TiboeIndexByRank:
//go18-examples/stdlib/sort/sortslice-before-go18.go
package main
import (
"fmt"
"sort"
)
type Lang struct {
Name string
Rank int
}
type TiboeIndexByRank []Lang
func (l TiboeIndexByRank) Len() int { return len(l) }
func (l TiboeIndexByRank) Less(i, j int) bool { return l[i].Rank < l[j].Rank }
func (l TiboeIndexByRank) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func main() {
langs := []Lang{
{"rust", 2},
{"go", 1},
{"swift", 3},
}
sort.Sort(TiboeIndexByRank(langs))
fmt.Printf("%v\n", langs)
}
$go run sortslice-before-go18.go
[{go 1} {rust 2} {swift 3}]
從上面的例子可以看到,我們要對[]Lang這個slice進行排序,我們就需要為之定義一個專門用于排序的類型:這里是TiboeIndexByRank,并讓其實現sort.Interface接口。使用過sort包的gophers們可能都意識到了,我們在為新的slice type實現sort.Interface接口時,那三個方法的Body幾乎每次都是一樣的。為了使得gopher們在排序slice時編碼更為簡化和便捷,減少copy&paste,Go 1.8為slice type新增了三個函數:Slice、SliceStable和SliceIsSorted。我們重新用Go 1.8的sort.Slice函數實現上面例子中的排序需求,代碼如下:
//go18-examples/stdlib/sort/sortslice-in-go18.go
package main
import (
"fmt"
"sort"
)
type Lang struct {
Name string
Rank int
}
func main() {
langs := []Lang{
{"rust", 2},
{"go", 1},
{"swift", 3},
}
sort.Slice(langs, func(i, j int) bool { return langs[i].Rank < langs[j].Rank })
fmt.Printf("%v\n", langs)
}
$go run sortslice-in-go18.go
[{go 1} {rust 2} {swift 3}]
實現sort,需要三要素:Len、Swap和Less。在1.8之前,我們通過實現sort.Interface實現了這三個要素;而在1.8版本里,Slice函數通過reflect獲取到swap和length,通過結合閉包實現的less參數讓Less要素也具備了。我們從下面sort.Slice的源碼可以看出這一點:
// $GOROOT/src/sort/sort.go
... ...
func Slice(slice interface{}, less func(i, j int) bool) {
rv := reflect.ValueOf(slice)
swap := reflect.Swapper(slice)
length := rv.Len()
quickSort_func(lessSwap{less, swap}, 0, length, maxDepth(length))
}
2、支持HTTP/2 Push
繼在Go 1.6版本全面支持 HTTP/2 之后,Go 1.8又新增了對 HTTP/2 Push 的支持。 HTTP/2 是在HTTPS的基礎上的下一代HTTP協議,雖然當前HTTPS的應用尚不是十分廣泛。而 HTTP/2 Push 是HTTP/2的一個重要特性,無疑其提出的初衷也仍然是為了改善網絡傳輸性能,提高Web服務的用戶側體驗。這里我們可以借用知名網絡提供商 Cloudflare blog 上的一幅示意圖來詮釋HTTP/2 Push究竟是什么:
從上圖中,我們可以看到:當Browser向Server發起Get page.html請求后,在同一條TCP Connection上,Server主動將style.css和image.png兩個資源文件推送(Push)給了Browser。這是由于Server端啟用了HTTP/2 Push機制,并預測判斷Browser很可能會在接下來發起Get style.css和image.png兩個資源的請求。這是一種典型的:“你可能會需要,但即使你不要,我也推給你”的處世哲學^0^。這種機制雖然在一定程度上能改善網絡傳輸性能(減少Client發起Get的次數),但也可能造成帶寬的浪費,因為這些主動推送給Browser的資源很可能是Browser所不需要的或是已經在Browser cache中存在的資源。
接下來,我們來看看Go 1.8是如何在net/http包中提供對HTTP/2 Push的支持的。由于HTTP/2是基于HTTPS的,因此我們先使用generate_cert.go生成程序所需的私鑰和證書:
// 在go18-examples/stdlib/http2-push目錄下,執行:
$go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1
2017/01/27 10:58:01 written cert.pem
2017/01/27 10:58:01 written key.pem
支持HTTP/2 Push的server端代碼如下:
// go18-examples/stdlib/http2-push/server.go
package main
import (
"fmt"
"log"
"net/http"
)
const mainJS = `document.write('Hello World!');`
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
pusher, ok := w.(http.Pusher)
if ok {
// If it's a HTTP/2 Server.
// Push is supported. Try pushing rather than waiting for the browser.
if err := pusher.Push("/static/img/gopherizeme.png", nil); err != nil {
log.Printf("Failed to push: %v", err)
}
}
fmt.Fprintf(w, `<html>
<head>
<title>Hello Go 1.8</title>
</head>
<body>
<img src="/static/img/gopherizeme.png"></img>
</body>
</html>
`)
})
log.Fatal(http.ListenAndServeTLS(":8080", "./cert.pem", "./key.pem", nil))
}
運行這段代碼,打開Google Chrome瀏覽器,輸入:https://127.0.0.1:8080,忽略瀏覽器的訪問非受信網站的警告,繼續瀏覽你就能看到下面的頁面(這里打開了Chrome的“檢查”功能):
從示例圖中的“檢查”窗口,我們可以看到 gopherizeme.png 這個image資源就是Server主動推送給客戶端的,這樣瀏覽器在Get /后無需再發起一次Get /static/img/gopherizeme.png的請求了。
而這一切的背后,其實是HTTP/2的ResponseWriter實現了Go 1.8新增的http.Pusher interface:
// $GOROOT/src/net/http/http.go
// Pusher is the interface implemented by ResponseWriters that support
// HTTP/2 server push. For more background, see
// https://tools.ietf.org/html/rfc7540#section-8.2.
type Pusher interface {
... ...
Push(target string, opts *PushOptions) error
}
3、支持HTTP Server優雅退出
Go 1.8中增加對HTTP Server優雅退出(gracefullly exit)的支持,對應的新增方法為:
func (srv *Server) Shutdown(ctx context.Context) error
和server.Close在調用時瞬間關閉所有active的Listeners和所有狀態為New、Active或idle的connections不同,server.Shutdown首先關閉所有active Listeners和所有處于idle狀態的Connections,然后無限等待那些處于active狀態的connection變為idle狀態后,關閉它們并server退出。如果有一個connection依然處于active狀態,那么server將一直block在那里。因此Shutdown接受一個context參數,調用者可以通過context傳入一個Shutdown等待的超時時間。一旦超時,Shutdown將直接返回。對于仍然處理active狀態的Connection,就任其自生自滅(通常是進程退出后,自動關閉)。通過Shutdown的源碼我們也可以看出大致的原理:
// $GOROOT/src/net/http/server.go
... ...
func (srv *Server) Shutdown(ctx context.Context) error {
atomic.AddInt32(&srv.inShutdown, 1)
defer atomic.AddInt32(&srv.inShutdown, -1)
srv.mu.Lock()
lnerr := srv.closeListenersLocked()
srv.closeDoneChanLocked()
srv.mu.Unlock()
ticker := time.NewTicker(shutdownPollInterval)
defer ticker.Stop()
for {
if srv.closeIdleConns() {
return lnerr
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}
我們來編寫一個例子:
// go18-examples/stdlib/graceful/server.go
import (
"context"
"io"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
exit := make(chan os.Signal)
signal.Notify(exit, os.Interrupt)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("Handle a new request:", *r)
time.Sleep(10 * time.Second)
log.Println("Handle the request ok!")
io.WriteString(w, "Finished!")
})
srv := &http.Server{
Addr: ":8080",
Handler: http.DefaultServeMux,
}
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Printf("listen: %s\n", err)
}
}()
<-exit // wait for SIGINT
log.Println("Shutting down server...")
// Wait no longer than 30 seconds before halting
ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
err := srv.Shutdown(ctx)
log.Println("Server gracefully stopped:", err)
}
在上述例子中,我們通過 設置Linux Signal的處理函數 來攔截Linux Interrupt信號并處理。我們通過context給Shutdown傳入30s的超時參數,這樣Shutdown在退出之前會給各個Active connections 30s的退出時間。下面分為幾種情況run一下這個例子:
a) 當前無active connections
在這種情況下,我們run上述demo,ctrl + C后,上述demo直接退出:
$go run server.go
^C2017/02/02 15:13:16 Shutting down server...
2017/02/02 15:13:16 Server gracefully stopped: <nil>
b) 當前有未處理完的active connections,ctx 超時
為了模擬這一情況,我們修改一下參數。讓每個request handler的sleep時間為30s,而Shutdown ctx的超時時間改為10s。我們再來運行這個demo,并通過curl命令連接該server(curl -v http://localhost:8080),待連接成功后,再立即ctrl+c停止Server,待約10s后,我們得到如下日志:
$go run server.go
2017/02/02 15:15:57 Handle a new request: {GET / HTTP/1.1 1 1 map[User-Agent:[curl/7.30.0] Accept:[*/*]] {} <nil> 0 [] false localhost:8080 map[] map[] <nil> map[] [::1]:52590 / <nil> <nil> <nil> 0xc420016700}
^C2017/02/02 15:15:59 Shutting down server...
2017/02/02 15:15:59 listen: http: Server closed
2017/02/02 15:16:09 Server gracefully stopped: context deadline exceeded
c) 當前有未處理完的active connections,ctx超時之前,這些connections處理ok了
我們將上述demo的參數還原,即request handler sleep 10s,而Shutdown ctx超時時間為30s,運行這個Demo后,通過curl命令連接該server,待連接成功后,再立即ctrl+c停止Server。等待約10s后,我們得到如下日志:
$go run server.go
2017/02/02 15:19:56 Handle a new request: {GET / HTTP/1.1 1 1 map[User-Agent:[curl/7.30.0] Accept:[*/*]] {} <nil> 0 [] false localhost:8080 map[] map[] <nil> map[] [::1]:52605 / <nil> <nil> <nil> 0xc420078500}
^C2017/02/02 15:19:59 Shutting down server...
2017/02/02 15:19:59 listen: http: Server closed
2017/02/02 15:20:06 Handle the request ok!
2017/02/02 15:20:06 Server gracefully stopped: <nil>
可以看出,當ctx超時之前,request處理ok,connection關閉。這時不再有active connection和idle connection了,Shutdown成功返回,server立即退出。
4、Mutex Contention Profiling
Go 1.8中runtime新增了對Mutex和RWMutex的profiling(剖析)支持。golang team成員,負責從go user角度去看待go team的work是否滿足用戶需求的 Jaana B. Dogan 在其個人站點上寫了一篇 介紹mutex profiling的文章 ,這里借用一下其中的Demo:
//go18-examples/stdlib/mutexprofile/mutexprofile.go
package main
import (
"net/http"
_ "net/http/pprof"
"runtime"
"sync"
)
func main() {
var mu sync.Mutex
var items = make(map[int]struct{})
runtime.SetMutexProfileFraction(5)
for i := 0; i < 1000*1000; i++ {
go func(i int) {
mu.Lock()
defer mu.Unlock()
items[i] = struct{}{}
}(i)
}
http.ListenAndServe(":8888", nil)
}
運行該程序后,在瀏覽器中輸入:http://localhost:8888/debug/pprof/mutex,你就可以看到有關該程序的mutex profile(耐心等待一小會兒,因為數據的采樣需要一點點時間^0^):
--- mutex:
cycles/second=2000012082
sampling period=5
378803564 776 @ 0x106c4d1 0x13112ab 0x1059991
構建該程序,然后通過下面命令:
go build mutexprofile.go
./mutexprofile
go tool pprof mutexprofile http://localhost:8888/debug/pprof/mutex?debug=1
可以進入pprof交互界面,這個是所有用過go pprof工具的gophers們所熟知的:
$go tool pprof mutexprofile http://localhost:8888/debug/pprof/mutex?debug=1
Fetching profile from http://localhost:8888/debug/pprof/mutex?debug=1
Saved profile in /Users/tony/pprof/pprof.mutexprofile.localhost:8888.contentions.delay.003.pb.gz
Entering interactive mode (type "help" for commands)
(pprof) list
Total: 12.98s
ROUTINE ======================== main.main.func1 in /Users/tony/Test/GoToolsProjects/src/github.com/bigwhite/experiments/go18-examples/stdlib/mutexprofile/mutexprofile.go
0 12.98s (flat, cum) 100% of Total
. . 17: mu.Lock()
. . 18: defer mu.Unlock()
. . 19: items[i] = struct{}{}
. . 20: }(i)
. . 21: }
. 12.98s 22:
. . 23: http.ListenAndServe(":8888", nil)
. . 24:}
ROUTINE ======================== runtime.goexit in /Users/tony/.bin/go18rc2/src/runtime/asm_amd64.s
0 12.98s (flat, cum) 100% of Total
. . 2192: RET
. . 2193:
. . 2194:// The top-most function running on a goroutine
. . 2195:// returns to goexit+PCQuantum.
. . 2196:TEXT runtime·goexit(SB),NOSPLIT,$0-0
. 12.98s 2197: BYTE $0x90 // NOP
. . 2198: CALL runtime·goexit1(SB) // does not return
. . 2199: // traceback from goexit1 must hit code range of goexit
. . 2200: BYTE $0x90 // NOP
. . 2201:
. . 2202:TEXT runtime·prefetcht0(SB),NOSPLIT,$0-8
ROUTINE ======================== sync.(*Mutex).Unlock in /Users/tony/.bin/go18rc2/src/sync/mutex.go
12.98s 12.98s (flat, cum) 100% of Total
. . 121: return
. . 122: }
. . 123: // Grab the right to wake someone.
. . 124: new = (old - 1<<mutexWaiterShift) | mutexWoken
. . 125: if atomic.CompareAndSwapInt32(&m.state, old, new) {
12.98s 12.98s 126: runtime_Semrelease(&m.sema)
. . 127: return
. . 128: }
. . 129: old = m.state
. . 130: }
. . 131:}
(pprof) top10
1.29s of 1.29s total ( 100%)
flat flat% sum% cum cum%
1.29s 100% 100% 1.29s 100% sync.(*Mutex).Unlock
0 0% 100% 1.29s 100% main.main.func1
0 0% 100% 1.29s 100% runtime.goexit
go pprof的另外一個用法就是在go test時,mutexprofile同樣支持這一點:
go test -mutexprofile=mutex.out
go tool pprof <test.binary> mutex.out
5、其他重要改動
Go 1.8標準庫還有兩個值得注意的改動,一個是:crypto/tls,另一個是database/sql。
在HTTPS逐漸成為主流的今天,各個編程語言對HTTPS連接的底層加密協議- TLS協議 支持的成熟度日益被人們所關注。Go 1.8給廣大Gophers們帶來了一個更為成熟、性能更好、更為安全的TLS實現,同時也增加了對一些TLS領域最新協議規范的支持。無論你是實現TLS Server端,還是Client端,都將從中獲益。
Go 1.8在crypto/tls中提供了基于ChaCha20-Poly1305的cipher suite,其中ChaCha20是一種stream cipher算法;而Poly1305則是一種code authenticator算法。它們共同組成一個TLS suite。使用這個suite,將使得你的web service或站點 具有更好的mobile瀏覽性能 ,這是因為傳統的AES算法實現在沒有 硬件支持 的情況下cost更多。因此,如果你在使用tls時沒有指定cipher suite,那么Go 1.8會根據硬件支持情況(是否有AES的硬件支持),來決定是使用ChaCha20還是AES算法。除此之外,crypto/tls還實現了更為安全和高效的 X25519 密鑰交換算法等。
Go 1.4以來,database/sql包的變化很小,但對于該包的feature需求卻在與日俱增。終于在Go 1.8這個dev cycle中, govendor 的作者 Daniel Theophanes 在 Brad Fitzpatrick 的“指導”下,開始對database/sql進行“大規模”的改善。在Go 1.8中,借助于context.Context的幫助,database/sql增加了Cancelable Queries、SQL Database Type、Multiple Result Sets、Database ping、Named Parameters和Transaction Isolation等新Features。在 GopherAcademy 的Advent 2016系列文章中,我們可以看到 Daniel Theophanes親手撰寫的文章 ,文章針對Go 1.8 database/sql包新增的features作了詳細解釋。
三、Go工具鏈(Go Toolchain)
在目前市面上的主流編程語言中,如果說Go的工具鏈在成熟度和完善度方面排第二,那沒有語言敢稱自己是第一吧^_^。Go 1.8在Go Toolchain上繼續做著持續地改進,下面我們來逐一看看。
1、Plugins
Go在1.8版本中提供了對Plugin的初步支持,并且這種支持僅限于Linux。plugin這個術語在不同語言、不同情景上下文中有著不同的含義,那么什么是Go Plugin呢?
Go Plugin為Go程序提供了一種在運行時加載代碼、執行代碼以改變運行行為的能力,它實質上由兩個部分組成:
- go build -buildmode=plugin xx.go 構建xx.so plugin文件
- 利用plugin包在運行時動態加載xx.so并執行xx.so中的代碼
C程序員看到這里肯定會有似曾相識的趕腳,因為這和傳統的動態共享庫在概念上十分類似:
go build -buildmode=plugin xx.go 類似于 gcc -o xx.so -shared xx.c
go plugin包 類似于 linux上的dlopen/dlsym或windows上的LoadLibrary
我們來看一個例子!我們先來建立一個名為foo.so的go plugin:
//go18-examples/gotoolchain/plugins/foo.go
package main
import "fmt"
var V int
var v int
func init() {
V = 17
v = 23
fmt.Println("init function in plugin foo")
}
func Foo(in string) string {
return "Hello, " + in
}
func foo(in string) string {
return "hello, " + in
}
通過go build命令將foo.go編譯為foo.so:
# go build -buildmode=plugin foo.go
# ldd foo.so
linux-vdso.so.1 => (0x00007ffe47f67000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f9d06f4b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9d06b82000)
/lib64/ld-linux-x86-64.so.2 (0x000055c69cfcf000)
# nm foo.so|grep Foo
0000000000150010 t local.plugin/unnamed-69e21ef38d16a3fee5eb7b9e515c27a389067879.Foo
0000000000150010 T plugin/unnamed-69e21ef38d16a3fee5eb7b9e515c27a389067879.Foo
000000000036a0dc D type..namedata.Foo.
我們看到go plugin的.so文件就是一個標準的Linux動態共享庫文件,我們可以通過nm命令查看.so中定義的各種符號。接下來,我們來load這個.so,并查找并調用相應符號:
//go18-examples/gotoolchain/plugins/main.go
package main
import (
"fmt"
"plugin"
"time"
)
func init() {
fmt.Println("init in main program")
}
func loadPlugin(i int) {
fmt.Println("load plugin #", i)
var err error
fmt.Println("before opening the foo.so")
p, err := plugin.Open("foo.so")
if err != nil {
fmt.Println("plugin Open error:", err)
return
}
fmt.Println("after opening the foo.so")
f, err := p.Lookup("Foo")
if err != nil {
fmt.Println("plugin Lookup symbol Foo error:", err)
} else {
fmt.Println(f.(func(string) string)("gophers"))
}
f, err = p.Lookup("foo")
if err != nil {
fmt.Println("plugin Lookup symbol foo error:", err)
} else {
fmt.Println(f.(func(string) string)("gophers"))
}
v, err := p.Lookup("V")
if err != nil {
fmt.Println("plugin Lookup symbol V error:", err)
} else {
fmt.Println(*v.(*int))
}
v, err = p.Lookup("v")
if err != nil {
fmt.Println("plugin Lookup symbol v error:", err)
} else {
fmt.Println(*v.(*int))
}
fmt.Println("load plugin #", i, "done")
}
func main() {
var counter int = 1
for {
loadPlugin(counter)
counter++
time.Sleep(time.Second * 30)
}
}
執行這個程序:
# go run main.go
init in main program
load plugin # 1
before opening the foo.so
init function in plugin foo
after opening the foo.so
Hello, gophers
plugin Lookup symbol foo error: plugin: symbol foo not found in plugin plugin/unnamed-69e21ef38d16a3fee5eb7b9e515c27a389067879
17
plugin Lookup symbol v error: plugin: symbol v not found in plugin plugin/unnamed-69e21ef38d16a3fee5eb7b9e515c27a389067879
load plugin # 1 done
load plugin # 2
before opening the foo.so
after opening the foo.so
Hello, gophers
plugin Lookup symbol foo error: plugin: symbol foo not found in plugin plugin/unnamed-69e21ef38d16a3fee5eb7b9e515c27a389067879
17
plugin Lookup symbol v error: plugin: symbol v not found in plugin plugin/unnamed-69e21ef38d16a3fee5eb7b9e515c27a389067879
load plugin # 2 done
... ...
我們來分析一下這個執行結果!
a) foo.go中的代碼也包含在main package下,但只是當foo.so被第一次加載時,foo.go中的init函數才會被執行;
b) foo.go中的exported function和variable才能被Lookup到,如Foo、V;查找unexported的變量和函數符號將得到error信息,如:“symbol foo not found in plugin”;
c) Lookup返回的是plugin.Symbol類型的值,plugin.Symbol是一個指向plugin中變量或函數的指針;
d) foo.go中的init在后續重復加載中并不會被執行。
注意:plugin.Lookup是goroutine-safe的。
在golang-dev group上,有人曾問過:buildmode=c-shared和buildmode=plugin有何差別?Go team member給出的答案如下:
The difference is mainly on the program that loads the shared library.
For c-shared, we can't assume anything about the host, so the c-shared dynamic library must be self-contained, but for plugin, we know the host program will be a Go program built with the same runtime version, so the toolchain can omit at least the runtime package from the dynamic library, and possibly more if it's certain that some packages are linked into the host program. (This optimization hasn't be implemented yet, but we need the distinction to enable this kind of optimization in the future.)
2、默認的GOPATH
Go team在Go 1.8以及后續版本會更加注重”Go語言的親民性”,即進一步降低Go的入門使用門檻,讓大家更加Happy的使用Go。對于一個Go初學者來說,一上來就進行GOPATH的設置很可能讓其感到有些迷惑,甚至有挫折感,就像建立Java開發環境需要設置JAVA_HOME和CLASSPATH一樣。Gophers們期望能做到Go的安裝即可用。因此Go 1.8就在這方面做出了改進:支持默認的GOPATH。
在Linux/Mac系下,默認的GOPATH為$HOME/go,在Windows下,GOPATH默認路徑為:%USERPROFILE%/go。你可以通過下面命令查看到這一結果:
$ go env
GOARCH="amd64"
GOBIN="/home/tonybai/.bin/go18rc3/bin"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/tonybai/go"
GORACE=""
GOROOT="/home/tonybai/.bin/go18rc3"
GOTOOLDIR="/home/tonybai/.bin/go18rc3/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build313929093=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
BTW,在Linux/Mac下,默認的GOROOT為/usr/local/go,如果你的Go環境沒有安裝到這個路徑下,在沒有設置$GOROOT環境變量的情況下,當你執行go subcommand相關命令時,你會看到如下錯誤:
$go env
go: cannot find GOROOT directory: /usr/local/go
3、其他變化
Go 1.8刪除了Go 1.7中增加的用于關閉ssa新后端的”-ssa=0” compiler flag,并且將ssa backend擴展到所有architecture中,對ssa后端也進一步做了優化。與此同時,為了將來進一步的性能優化打基礎,Go 1.8還引入了一個新編譯器前端,當然這對于普通Gopher的Go使用并沒有什么影響。
Go 1.8還新增go bug子命令,該命令會自動使用默認瀏覽器打開new issue頁面,并將采集到的issue提交者的系統信息填入issue模板,以幫助gopher提交符合要求的go issue,下面是go bug打開的issue page的圖示:
四、性能變化(Performance Improvement)
無論是Gotoolchain、還是runtime(包括GC)的性能,一直都是Go team重點關注的領域。本次Go 1.8依舊給廣大Gophers們帶來了性能提升方面的驚喜。
首先,Go SSA 后端擴展到所有architecture和新編譯器前端的引入,將會給除X86-64之外架構上運行的Go代碼帶來約20-30%的運行性能提升。對于x86-64,雖然Go 1.7就已經開啟了SSA,但Go 1.8對SSA做了進一步優化,x86-64上的Go代碼依舊可能會得到10%以內的性能提升。
其次,Go 1.8持續對Go compiler和linker做性能優化,和1.7相比,平均編譯鏈接的性能提升幅度在15%左右。雖然依舊沒有達到Go 1.4的性能水準。不過,優化依舊在持續進行中,目標的達成是可期的。
再次,GC在低延遲方面的優化給了我們最大的驚喜。在Go 1.8中,由于消除了GC的“ stop-the-world stack re-scanning ”,使得GC STW(stop-the-world)的時間通常低于100微秒,甚至經常低于10微秒。當然這或多或少是以犧牲“吞吐”作為代價的。因此在Go 1.9中,GC的改進將持續進行,會在吞吐和低延遲上做一個很好的平衡。
最后,defer的性能消耗在Go 1.8中下降了一半,與此下降幅度相同的還有通過cgo在go中調用C代碼的性能消耗。
五、小結兼參考資料
Go 1.8的變化不僅僅是以上這些,更多變化以及詳細的描述請參考下面參考資料中的“Go 1.8 Release Notes”:
- Go 1.8
- Go 1.8 Release Notes
- Go 1.8 Release Notes(before release)
- Announcing Support for HTTP/2 Server Push
- What’s coming in Go 1.8
以上demo中的代碼在 這里 可以找到。
? 2017,bigwhite. 版權所有.
來自:http://tonybai.com/2017/02/03/some-changes-in-go-1-8/