每日最新頭條.有趣資訊

解讀Go語言的2018:怎麽就在中國火成這樣了?

作者 | 郝林

編輯 | 小智

本篇文章是 Go 語言 2018 年終盤點,力求客觀、深入分析 2018 年 Go 語言的技術發展現狀,同時對明年可能的發展情況進行預測和展望。

今年真可謂是不平靜的一年,前有人工智能國家級戰略的發布,行業已經在大跨步的挺進,但人才缺口每天都在擴大;後有區塊鏈技術從爆發式增長到大幅回落,無數程式員蜂擁而至,又在現如今變得手足無措。

那麽,Go 語言在 2018 年這一年發展得又如何呢?它的下一步又將會怎樣?且聽筆者細細道來。

首先,筆者要說的是,在 TIOBE 於 2018 年 11 月份公布的編程語言排行榜中,Go 語言已然擠到了前 10 的位置。雖然這與去年同期的第 14 位看起來相差不大,但卻是一個裡程碑式的進步。

從 Google Trends 提供的流行趨勢統計來看,在過去的 12 個月裡,Go 語言的流行也是持續升溫的。

這種升溫雖然並不算快,但是很持久。這對編程語言的生態環境和人才的發展是非常有利的。

此外,完全不出乎我們的意料:中國依然是 Go 語言愛好者最多的國家,沒有之一。

具有諷刺意味的是,作為 Go 語言誕生地的美國,僅排在了第 15 位。我們對先進技術和前沿科技的熱衷絕對是不輸他國的。下面,讓我們再把尺度縮小到城市級別。

顯然,在我國,北京、深圳、上海這三個城市聚集了非常多的 Go 語言程式員和工程師。尤其是北京,簡直是 Go 語言愛好者的聖地啊!

至於北京博得頭籌的原因,據筆者觀察,首先肯定是:在北京的互聯網公司很多,起碼明顯多於其他的一、二線城市。Go 語言如今在互聯網公司中非常流行,即使有的公司高層並沒有批準大規模地使用 Go 語言,但是工程師們都在做積極的嘗試。

其次,北京做雲計算的公司很多,不論是面向市場的公有雲還是自建自用的私有雲。說到雲計算,我們就不得不提及開放平台技術、容器技術、集群管理技術,以及現在很火熱的微服務(Microservices)和 Serverless 技術,等等。而這些,恰恰都是 Go 語言的專長。在這些方面,有很多成熟的基於 Go 語言的解決方案可供選擇。

再次,北京的高科技創業公司非常多。他們往往沒有歷史包袱、勇於創造和嘗試。在做技術選型的時候,他們也更容易選擇 Go 語言。因為,Go 語言既擁有編譯型編程語言固有的高運行效率,又具有解釋型編程語言常有的高開發效率。而且,Go 語言還不像有些編程語言那樣時不時地出現內鬥、分裂等混亂情況,當然也沒有無良的技術持有者吵鬧著要對編程語言的商用進行收費。

Go 語言在語言規範的發展、版本的迭代和開發者生態的建設方面都非常的穩定,並有著良好的包容性和兼容性。保持簡單、面向契約和利於協作是 Go 語言最突出的設計哲學。無論是做軟體原型,還是用於小團隊作戰,又或是進行大規模的研發,Go 語言都會是很不錯的選擇。

最後,很多喜愛 Go 語言、致力於推廣 Go 語言技術的個人開發者、技術團隊、互聯網公司以及知識服務廠商也都在北京。這都直接或間接地導致了 Go 語言在這座城市的流行。

好了,到這裡,筆者相信你已經對 Go 語言在中國的流行有了一定的了解。下面,我們再來說說 Go 語言在 2018 年具體都有哪些進展。

首先說一下,關於 Go 語言在 2018 年之前的具體進展,筆者推薦你去看這幾篇同系列文章,如下:

語法和平台

Go 語言官方團隊在 2018 年 2 月正式發布它的 1.10 版本。不同於其他很多被稱為版本帝的編程語言,到了這樣一個版本號 10,Go 1 在語言規範方面已經幾乎沒有什麽改動了,一些語法上的小小增強也並不值得我們特別關注。而在 2018 年 8 月發布的 Go 1.11 更是沒有任何語言規範方面的變動。

Go 語言對於本身的向後兼容性保持得非常好,高版本對低版本中的語言語法、工具和標準庫都不會有任何破壞。然而,Go 語言在其支持的作業系統方面還是很大刀闊斧的。這體現在,Go 1.10 不再支持 10.3 以下版本的 FreeBSD 和 8.0 以下版本的 NetBSD。並且,這個版本也是支持 OpenBSD 6.0、OS X 10.9 以及 Windows XP 和 Windows Vista 的最後一個版本。在這些作業系統之上編寫或運行 Go 語言程式的開發者們要注意。

環境和工具

使用過 Go 語言的開發者們都知道,當把 Go 語言的預編譯包解壓到某個目錄後,我們還需要至少設定兩個環境變量——GOROOT 和 GOPATH。前者代表直接包含 Go 語言本身的那個目錄路徑,而後者則用於指定可放置第三方庫和自有代碼的工作區(或者說工作目錄)的路徑。

一個好消息是,自 Go 語言的 1.10 版本起,GOROOT 這個環境變量就沒有必要設定了。如果我們不設定它,那麽 Go 的標準工具會嘗試以自身所在的目錄為基礎,自動地推斷出 GOROOT 應該指向的目錄路徑。

另外,從這個版本開始,我們可以自行地設定 Go 語言的臨時目錄路徑了,設定的途徑是設定環境變量 GOTMPDIR。Go 語言的臨時目錄主要用於存放 Go 工具在編譯或測試程式時產生的各種臨時檔案。在這之前,這些臨時檔案都會被存放到固定的地方,此地的具體路徑會根據作業系統的不同而不同,一般會位於作業系統的臨時目錄的某個子目錄下。自定義這個目錄的好處在於,可以讓我們方便地觀察編譯過程,並查看編譯或測試的中間結果。

說到編譯,筆者一定要提一下 1.10 版本的另一項改進,這與 go build 命令有關。以前,如果我們要強行地重新構建所有相關的代碼包,那麽就需要在運行這個命令的時候追加標記“-a”。而現在,我們無需這樣做了。go build 命令會根據源碼檔案內容、構建標記和編譯元數據,自動地決定什麽時候應該重新構建那些代碼包。這項工作再也不需要人工乾預了。

與此項改進相關的變化是,go build 命令現在總是會把最近的構建結果緩存起來,以便在將來的構建中重用。我們可以通過運行 go env GOCACHE 命令來查看緩存目錄的路徑。緩存的數據總是能夠正確地反映出當時的源碼檔案、構建環境和編譯器選項等的真實情況。一旦有任何變動,緩存數據就會失效,go build 命令就會再次真正地執行構建。因此,我們並不用擔心緩存數據體現的不是實時的結果。實際上,這正是上述改進能夠有效的主要原因。go build 命令會定期地刪除最近未使用的緩存數據,但如果你想手動刪除所有的緩存數據,運行一下 go clean -cache 命令就好了。

順便說一下,對於測試成功的結果,go 命令也是會緩存的。運行 go clean -testcache 命令將會刪除掉所有的測試結果緩存。不過別擔心,這樣做肯定不會刪除任何的構建結果緩存,它們是兩碼事。

此外,設定環境變量 GODEBUG 的值也可以稍稍地改變 go 命令的緩存行為。比如,設定值為 gocacheverify=1 將會導致 go 命令繞過任何的緩存數據,而真正地執行操作並重新生成所有結果,然後再去檢查新的結果與現有的緩存數據是否一致。

再來說 go install 命令。現在,go install 命令在默認情況下只會去安裝我們明確指定的那些代碼包。這些代碼包依賴的那些包並不會被安裝。這同樣得益於構建結果緩存,它可以使安裝的速度得到明顯的提升。如果你想要強製地安裝依賴包,那麽請在運行命令的時候追加“-i”標記。

程式測試

前面我們說過了,測試成功的結果也會被緩存。如果 go test 命令確定可以使用被緩存的結果,那麽它列印出的內容也會出自於緩存。這時,被列印的內容中會包含“(cached)”字樣。

另外,go test 命令現在會自動地運行 go vet 命令,以便在真正運行測試之前識別出一些程式編寫方面的問題。我們都知道,go vet 命令用於對 Go 語言源碼進行靜態檢查,並報告已發現的可疑問題。這些問題一般都是符合語法規則的,因此編譯器無法查出它們。但是,它們很有可能代表了對某些程式實體(或者說 API)的錯誤使用。雖然 go vet 命令有時候並不能保證它報告的每一個問題都是真正的問題,但它卻可以給予我們一份重要的參考,以便讓我們在編程的過程中小心行事。

與 Go 語言提供的很多高級功能一樣,我們也可以阻止 go test 命令自動運行 go vet 命令,這需要在運行前者的時候追加“-vet=off”這個標記。

最後,關於 go test 命令,還有兩個值得注意的新標記——“-failfast”和“-json”。顧名思義,“-failfast”標記可以讓 go test 命令一旦發現有測試失敗的情況就立即忽略掉剩餘的測試並終止運行。不過要注意,如果存在與失敗的測試並發進行的測試的話,那麽後者還是會繼續運行直至完成的。“-json”標記對於程式測試的自動化大有裨益。它會讓 go test 命令產生 JSON 格式的測試報告,這使得其他程式很容易讀入和處理。

程式文檔

關於程式文檔,只有一點需要我們注意。Go 1.11 是 godoc 命令支持命令行接口的最後一個版本。** 在未來的版本中,我們運行 godoc 命令的時候,它會啟動一個 Web 伺服器,以便讓我們直接進入圖形化界面進行文檔查詢。

程式性能分析

現在,runtime/pprof 代碼包中的 Lookup 函數已經支持了更加多樣的參數值。這就意味著,Go 語言的程式性能分析現在可以生成和解讀更多視角下的分析報告了。我們可以把這樣的分析報告包含的內容叫做程式性能概要資訊(簡稱概要資訊),並把存儲這些分析報告的檔案叫做概要檔案。

Lookup 函數可以生成的概要資訊目前共有 6 種。這 6 種概要資訊分別由字元串類型的參數值 goroutine、heap、allocs、threadcreate、block 和 mutex 代表。下面是它們代表的含義:

goroutine:收集當前正在使用的所有 goroutine 的堆棧跟蹤資訊。

heap:收集與堆記憶體的分配和釋放有關的采樣資訊,默認以在用空間(inuse_space)的視角呈現。

allocs:同樣收集與堆記憶體的分配和釋放有關的采樣資訊,但默認以已分配空間(alloc_space)的視角呈現。

threadcreate:收集一些特定的堆棧跟蹤資訊,其中的調用鏈上的代碼都導致了新的作業系統線程的產生。

block:收集因爭用同步原語而被阻塞的那些代碼的堆棧跟蹤資訊。

mutex:曾經作為同步原語持有者的那些代碼的堆棧跟蹤資訊。

這裡所說的同步原語,指的是存在於 Go 語言運行時系統內部的一種底層同步工具,或者說一種同步機制。它是直接面向記憶體地址的,並以異步信號量和原子操作作為實現手段。我們已經熟知的通道、互斥鎖、條件變量、“WaitGroup”以及 Go 語言運行時系統本身,都會利用它來實現自己的功能。

另外,在用空間和已分配空間的區別是,前者指的是已經分配但還沒有被回收的空間,而後者隻關注分配出的空間,不論它們是否已經被回收。

注意,如果我們在運行 go test 命令的時候追加了標記“-memprofile”,那麽該命令會通過底層的 API 以 allocs 為視角生成概要資訊和概要檔案。這相當於對從測試開始時的所有已分配字節進行記錄,包含已經被垃圾回收器收回的那些字節。在 Go 1.11 版本之前,go test 命令在這種情況下採用的是 heap 視角。

最後,go tool pprof 工具已經可以正確地單獨讀取和處理所有種類的概要檔案了。這得益於,從 Go 1.10 版本開始,block 和 mutex 視角下的概要資訊已經完善。在這之前,我們使用 go tool pprof 查閱這兩種概要檔案的時候,還不得不同時指定相應程式的二進製檔案。

運行時系統

需要特別注意,runtime 代碼包中的 LockOSThread 函數和 UnlockOSThread 函數的行為已經發生了變化。我們都知道,前一個函數的功能是將當前的 goroutine 與那一時刻正在承載這個 goroutine 運行的作業系統線程進行綁定。在綁定之後,這個 goroutine 就只能由該作業系統線程運行了,反之,該作業系統線程也只能運行這一個 goroutine 了。顯而易見,runtime. UnlockOSThread 函數的功能是解除上述綁定關係。當然了,這兩個函數都只能作用於它們被調用時所在的那個 goroutine。

以前,runtime. LockOSThread 函數是冪等的。也就是說,無論我們在同一個 goroutine 中調用了它多少次,都隻相當於調用了一次。另一方面,只要我們調用一次 runtime. UnlockOSThread 函數,就總是能夠解除針對於當前 goroutine 的這種綁定。

但是,從 Go 語言的 1.10 版本開始,在我們想要完全解除綁定的時候,可能就需要調用多次 runtime. UnlockOSThread 函數才能夠實現了。至於具體需要調用多少次完全取決於,當初在同一個 goroutine 中調用 runtime. LockOSThread 函數的次數。換句話說,只有進行相同次數的函數調用,才能讓當前 goroutine 與某個作業系統線程之間的綁定關係完全解除。我們可以把現在的這種對應關係理解為是基於嵌套的,可以想象一下:當初包裝了多少層紙箱,現在就要拆開多少層紙箱。

其實一直以來,有很多第三方 Go 語言庫的作者都誤以為對於這兩個函數的調用就是基於嵌套關係的。不過無論怎樣,我們現在都應該仔細檢查代碼並小心的應對了。

筆者認為,如果你確實需要進行這種綁定,那麽就應該基於這兩個函數封裝一個數據結構。在這個數據結構中,至少應該包含一個用於記錄調用 runtime. LockOSThread 函數次數的資料欄,以方便後續的解綁操作。

在 2018 年,對於 Go 語言的運行時系統來說,我們可以輕易感知到的變化基本上只有這一個。不過,非常多的改進和優化都在悄無聲息的進行著,有的已經完成了,而有的還在進展之中。已完成的改進如:在通常情況下,我們傳遞給 runtime.GOMAXPROCS 函數的參數值已經不再受限了,只要它在 int32 類型可容納的範圍之內就可以。

標準庫

在 Go 語言的 1.10 和 1.11 這兩個版本中,官方團隊與社區開發者們一起對標準庫做了大量的改進。可喜可賀,社區開發者對 Go 語言的貢獻次數現在已經超過官方團隊了!

由於這方面的改進繁多,也由於筆者在新近發布的極客時間專欄《Go 語言核心 36 講》中已經詳細講解了不少,所以這裡就不再贅述了。

兩個新實驗

我們再來說說 Go 1.11 的兩個新實驗吧,一個是對 WebAssembly 的實驗性支持,另一個是推出由 dep 和 vgo 演化而來的依賴管理機制和新概念 module。

按照官方的描述,WebAssembly(縮寫為 WASM)是一種二進製指令格式,它針對的是以堆棧為基礎的虛擬機。WASM 有很好的可移植性,以便讓 C++、Golang、Rust 等高級編程語言來操控它,並有能力部署到 Web 程式上。

用國語來說,WASM 提供了一種途徑,可以讓我們用後端編程語言直接去編寫 Web 頁面中的邏輯。在 Go 1.11 中,我們可以很輕易地把 Go 語言源碼檔案轉換為 WASM 格式的檔案,然後在 Web 頁面中通過寥寥幾行 JavaScript 代碼引用這個檔案並把其中的邏輯發布到頁面上。WASM 的 1.0 版本現在已經支持了絕大多數的主流網絡瀏覽器,比如:Chrome、Firefox、Safari 等。如果想了解具體的玩法,你可以參看這個 wiki 頁面。

筆者對 Go 語言官方的這種探索性實驗一直都持讚成的態度,不論是前些年的移動端(Android 和 iOS)方向,還是今年的 Web 端(WASM)方向。不過,筆者依然覺得 Go 語言的優勢在服務端,現在很明顯,而且在可預見的未來也應該是如此。所以,對於這些多端探索,筆者建議大家“保持關注,積極試驗,但不要偏移重心”。

相比之下,筆者倒是更加看好 Go 語言新放出的依賴管理機制。Go 語言愛好者們都知道,Go 語言在這方面一直是缺失的。雖然目前存在幾個不錯的第三方解決方案,但是沒有一個是可以脫穎而出的,同時官方也一直沒有給出一個統一的標準。

經過了一段時間的試驗和演化,Go 語言官方的依賴管理機制終於脫胎於 dep 和 vgo。雖然其間存在一些摩擦和風波,但是結果終歸是積極的。

在 Go 語言新的依賴管理機制中,module 是一個非常重要的概念。簡單來說,module 象徵著由某個 Go 語言代碼包以及它依賴的代碼包共同組成的一個獨立單元。這裡的 Go 語言代碼包和它依賴的那些代碼包都是版本化的。一個 module 的根目錄下總是直接存有一個名為 go.mod 的檔案。這個檔案中會包含當前 module 的路徑,以及它依賴的那些 module 的路徑和版本號。如此一來,對於每一個版本的 module,它依賴的所有代碼都會被固化下來。這對於後續的版本管理和 module 重建來說都是重要的基礎。詳情可以參看這裡的 wiki 頁面。

不過,不要忘了,Go 1.11 中包含的這個依賴管理機制是實驗性的。其中的任何部分都有可能由於社區的反饋和官方的改進而變化。所以,你在正式使用它之前一定要考慮到後續可能存在的變更成本。雖然如此,筆者仍然會鼓勵廣大開發者們去積極使用和反饋。想象一下 maven 對於 Java 世界的重要性吧。筆者相信,我們心目中的 Go 項目依賴管理機制已經離此不遠了。

Go 1.12

筆者首先盼望的肯定是 Go 語言依賴管理機制的第一個穩定版,並且相信很多 Go 語言愛好者都是如此。但是,在筆者看來,這個穩定版本並不一定就會在 Go 語言的 1.12 版本中發布,雖然目標是這樣的。

正如前文所述,Go 1.12 會從 godoc 命令中去掉命令行接口,而隻保留基於 Web 的圖形化查詢界面。同時,它也不再允許開發者通過 GOCACHE 環境變量去禁用構建結果緩存。當然了,這個版本也會包含大量針對標準庫的改進,詳細內容可以到此版本的發布說明草稿中查看。

https://tip.golang.org/doc/go1.12

Go 2

在去年我們就說過,Go 語言官方已經把 Go 2 的計劃鄭重地擺上了桌面。今年的進展是,Go 2 的設計草案已經發布了。

Go 語言作者之一 Robert Griesemer 不久前剛剛在官方部落格發文稱,Go 2 已經選擇出備選新特性提案,進入提案反饋階段,他呼籲社區積極參與進來,和官方團隊一起改進 Go 語言設計。具體詳情可以看 InfoQ 的報導《Go 2 提上日程,官方團隊呼籲社區給新特性提案提交反饋》

目前來看,Go 2 將會主要解決三個問題,即:錯誤處理、錯誤值以及對泛型自定義的支持。

從多年前開始,很多 Go 程式開發者就已經在抱怨 Go 語言在錯誤處理方面的醜陋了。Go 函數的多返回值使我們可以在返回一般結果值的同時攜帶錯誤值。這是一個很亮眼的特性,可以讓我們重視錯誤,並總是進行明確的處理。不過,這也帶來了一個問題。我們在調用這樣的 Go 函數之後,不得不先用 if 語句檢查錯誤值是否為 nil,然後才能進行下一步處理。如果在我們的程式中有很多這樣的代碼,那麽顯然是很醜陋的。

不過,筆者認為,這很多都是開發者在程式設計方面存在問題導致的。然而,我們也並不能否認,Go 語言的這種錯誤處理方式是很多程式變得醜陋的導火索。不論怎樣,Go 語言官方已經開始正視這個問題並在著手解決了。

與之相關的一個問題就是錯誤值的設計。我們知道,只要實現了 error 接口的數據類型就都可以被稱為錯誤類型,它們的值就可以被稱為錯誤值。創造一個錯誤值的方式有很多,調用 errors.New 函數、調用 fmt.Errorf 函數,以及使用值的字面量,等等。這恰恰使我們在對錯誤種類做判斷的時候不得不仔細地選擇判斷方式,是檢查錯誤值的類型?還是判斷它是否等於某個已存在的錯誤值?又或者是對錯誤描述進行匹配?這顯然增加了錯誤處理的成本。從草案上來看,已經有一些顯著的成果了,我們還是拭目以待吧。

關於泛型,筆者並不想多說。允許泛型的自定義顯然可以增強編程語言的表達能力,並且在一些場景下可以顯著地減少重複的代碼。不過,怎樣將它設計好,並用優雅的方式展現出來,是一個很複雜的問題。在相應的草案中,Go 語言官方給出了一個看起來還不錯的方案,但是依然可能存在變數。希望官方能夠參考 C++、Java、Rust、Swift 等編程語言的設計,取其精華、去其糟粕吧。

關於詳細的 Go 2 設計草案,大家可以到這裡查閱。

https://go.googlesource.com/proposal/+/master/design/go2draft.md

社區與環境

筆者在今年明顯的感覺到,關注 Go 語言的各路人馬又變多了。這體現在了幾個方面。首先,以 Go 語言為主題的 meetup 明顯增多。無論是哪個技術組織發起的,參與的人都不在少數。而且,這樣的 meetup 已經在更多的一、二線城市中出現了。

其次,互聯網上的 Go 語言中文資料(比如部落格、教程、電子書等)也明顯增多,不論是免費的還是收費的,雖然水準各不相同,但是顯然大家都在進行積極的探索和分享。

最後,很多主打技術培訓的公司和組織都已經對 Go 語言進行了重點的關注,並開發出了自己的培訓產品或服務,包括線上的知識付費產品、線下的面授課程,以及目標各有不同的開源項目,等等。筆者也有幸參與其中,並在極客時間開設了專欄《Go 語言核心 36 講》。

不過,隨著 Go 語言逐漸得到各方的廣泛關注,盜版和抄襲也日益猖獗。筆者在這裡呼籲,希望大家能夠尊重原創作者的辛勤勞動和知識產權,拒絕盜版、抵製抄襲!只有這樣才能夠讓作者們更加積極地產出優秀的內容,我們的學習環境才能更美好,技術社區才能因此向著健康、壯大的方向發展。

以上,就是我對 Go 語言在 2018 年發展的簡要回顧和對其未來發展的展望。希望能夠借此促使大家對 Go 語言和我們國內的技術社區有更多的關注。

郝林,國內知名的 Go 語言技術布道者,GoHackers 技術社群的發起人和組織者。他也是極客時間專欄《Go 語言核心 36 講》的作者,以及圖靈原創圖書《Go 並發編程實戰》的作者。他曾在輕鬆籌任大數據負責人,同時負責大數據部門和主站的後端技術團隊。

點一下好看試試微信的新功能?

獲得更多的PTT最新消息
按讚加入粉絲團