您當前的位置:首頁 > 書法

Go 語言十年而立,Go2 蓄勢待發

作者:由 CSDN 發表于 書法時間:2019-07-26

原創: 柴樹杉

Go 語言十年而立,Go2 蓄勢待發

作者 | 柴樹杉

責編 | 郭 芮

出品 | CSDN(ID:CSDNnews)

在21世紀的第一個十年,計算機在中國大陸才逐漸開始普及,高校的計算機相關專業也逐漸變得熱門。當時學校主要以C/C++和Java語言學習為主,而這些語言大多是上個世紀90年代或更早誕生的,因此這些計算機領域的理論知識或程式語言彷彿是上帝創世紀時的產物,作為計算機相關專業的學生只能仰望這些成果。

Go語言誕生在21世紀新一波工業程式語言即將爆發的時期。在2010年前後誕生了編譯型語言Rust、Kotlin和Switft語言,前端誕生了Dart、TypeScript等工業型語言,最新出現的V語言更甚至嘗試站在Go和Rust語言肩膀之上創新。而這些變化都發生在我們身邊,讓中國的計算機愛好者在學習的過程中見證歷史的發展,甚至有機會參與其中。

2019年是CSDN的二十週年,也是Go語言面世十週年。感謝CSDN平臺提供的機會,讓筆者可以跟大家分享十年來中國Go語言社群的一些故事。

#FormatImgID_2#

Go語言誕生

Go語言最初由Google公司的Robert Griesemer、Ken Thompson和Rob Pike三位大牛於2007年開始設計發明的。

其設計最初的洪荒之力來自於對超級複雜的C++11特性的吹捧報告的鄙視,最終目標是設計網路和多核時代的C語言。

到2008年中期,語言的大部分特性設計已經完成,並開始著手實現編譯器和執行,大約在這一年Russ Cox作為主力開發者加入。到了2009年,Go語言已經逐步趨於穩定。同年9月,Go語言正式釋出並開源了程式碼。

Go 語言十年而立,Go2 蓄勢待發

以上是《Go語言高階程式設計》一書中第一章第一節的內容。Go語言剛剛開源的時候,大家對它的編譯速度印象異常深刻:秒級編譯完成,幾乎像指令碼一樣可以馬上編譯並執行。同時Go語言的隱式介面讓一個編譯型語言有了鴨子型別的能力,筆者也第一次認識到原來C++的虛表vtab也可以動態生成!至於大家最願意討論的並非特性,其實並不是Go語言新發明的基石,早在上個世紀的八九十年代就有諸多語言開始陸續嘗試將CSP理論引入程式語言(Rob Pike是其中堅定的實踐者)。只不過早期的CSP實踐的語言沒有進入主流開發領域,導致大家對這種併發模式比較陌生。

除了語言特性的創新之外,Go語言還自帶了一套編譯和構建工具,同時小巧的標準庫攜帶了完備的Web程式設計基礎構建,我們可以用Go語言輕鬆編寫一個支援高併發訪問的Web服務。

作為網際網路時代的C語言,Go語言終於強勢進入主流的程式設計領域。

#FormatImgID_4#

Go語言十年奮進

Go從2007年開始設計,在2009年正式對外公佈,至今剛好十年。十年來Go語言以穩定著稱,Go1。0的程式碼在2019年依然可以不用修改直接被編譯執行。但是在保持語言穩定的同時,Go語言也在逐步夯實基礎,十年來一直向著完美的極限逼近。讓我們看看這十年來Go語言有哪些變化。

介面變化

首先是看看介面的變化。第一次是在2009剛開源的時候,這時候可以說是Go語言的上古時代。Go語言的主頁如下:

Go 語言十年而立,Go2 蓄勢待發

那個年代的Gopher們,使用的是hg工具下載程式碼(而不是Git),Go程式碼是在Google Code託管(而不是GitHub)。隨著程式碼的發展,hg已經慢慢淡出Gopher視野,Google Code網站也早已經關閉,而Go1之前的上古時代的Go老程式碼已經開始慢慢腐化了。

首頁中心是Go語言最開始的口號:Go語言是富有表現力的、併發的程式語言,並且是簡潔的。同時給了一個“Hello, 世界”的例子(注意,這裡的“世界”是日文)。

然後右上角是初學者的樂園:首先是安裝環境,然後可能是早期的三日教程,第三個是標準庫的使用。右上角的圖片是Russ Cox的一個影片,在Youtube應該還能找到。

左上角是Go實戰的那個經典文件。此外FAQ、語言規範、記憶體模型是非常重要的核心溫度。左下角還有cmd等文件連結,子頁面的內容應該沒有什麼變化。

然後在2012年準備釋出第一個正式版本Go1,在Go1之前語言、標準庫和godoc都進行了大量的改進。Go1風格的頁面效果如下:

Go 語言十年而立,Go2 蓄勢待發

新頁面剛出來的時候有眼睛一亮的感覺,這個是目前存在時間最長久的頁面佈局。但是不僅僅是筆者我,甚至Go語言官方也慢慢對中國頁面有點審美疲勞了。因此,從2018年開始Go語言開始新的Logo和網站的重新設計工作。

下面的是Go語言新的Logo:

Go 語言十年而立,Go2 蓄勢待發

2019年是對Go語言發展極其重要的一年,今年8月將釋出Go1。13,而這個版本將正式重啟Go語言語法的進化,向著Go2前進。而新的網站已經在Go1。13正式釋出之前的7月份就已經上線:

Go 語言十年而立,Go2 蓄勢待發

頭部的按鈕風格的選單變成了平鋪的風格,顯得更加高大上。同時頁面的顏色做了調整,保持和新Logo顏色一致。頁面的佈局也做了調整,將下載左右兩列做了調換。同時地鼠的腦袋歪到一邊,估計是頸椎病復發了。

總的來說,Go語言官網主頁經歷了Go1前、Go1(1。0~1。10)、Go1後(或者叫Go2前)三個階段,分別對應3種風格的頁面。新的佈局或許會成為下個十年Go2的主力頁面。

語法變化

Go語言雖然從2009年誕生,但是到了2012年才釋出第一個正式的版本Go1。其實在Go1誕生之前Go語言就已經足夠穩定了,國內的七牛雲從Go1之前就開始大力轉向Go語言開發,是國內第一家廣泛採用Go語言開發的網際網路公司。Go1的目標是梳理語法和標準庫陰暗的角落,為後續的10年打下堅實的基礎。

從目前的結果看,Go1無疑是取得了極大的成果,Go1時代的程式碼依然可以不用修改就可以用最新的Go語言工具編譯構建(不包含CGO或組合語言部分,因為這些外延的工具並不在Go1的承諾範圍)。但是Go1之後依然有一些語法的更新,在Go1。10前的Go1時代語法和標準庫部分的重大變化主要有三個:

第一個重大的語法變化是在2012年釋出的Go1.2中,給切片語法增加了容量的控制

,這樣可以避免不同的切片不小心越界訪問有著相同底層陣列的其它切片的記憶體。

第二個重大的變化是2016年釋出的Go1.7標準庫引入了context包。

context包是Go語言官方對Go進行併發程式設計的實踐成果,用來簡化對於處理單個請求的多個Goroutine之間與請求域的資料、超時和退出等操作。context包推出後就被社群快速吸收使用,例如gRPC以及很多Web框架都透過context來控制Goroutine的生命週期。

第三個重大的語法變化是2017年釋出的Go1.9 ,引入了類型別名的特性:type T1 = T2。

其中類型別名T1是透過=符號從T2定義,這裡的T1和T2是完全相同的型別。之所以引入類型別名,很大的原因是為了解決Go1。7將context擴充套件庫移動到標準庫帶來的問題。因為標準庫和擴充套件庫中分別定義了context。Context型別,而不同包中的型別是不相容的。而gRPC等很多開源的庫使用的是最開始以來的擴充套件庫中的context。Context型別,結果導致其無法和Go1。7標準庫中的context。Context型別相容。這個問題最終透過類型別名解決了:擴充套件庫中的context。Context型別是標準庫中context。Context的別名型別,從而實現了和標準庫的相容。

此外還有一些語法細節的變化,比如Go1。4對for迴圈語法進行了增強、Go1。8放開對有著相同記憶體佈局的結構體強制轉型限制。讀者可以根據自己新需要檢視相關釋出日誌的文件說明。

執行時的變化

執行時部分最大的變化是動態棧部分。在Go1。2之前Go語言採用分段棧的方式實現棧的動態伸縮。但是分段式動態棧有個效能問題,因為棧記憶體不連續會導致CPU快取命中率下降,從而導致熱點的函式呼叫效能受到影響。因此從Go1。3開始該有連續式的動態棧。連續式的動態棧雖然部分緩解了CPU 快取命中率問題(依然存在棧的切換問題,這可能導致CPU快取失效),但同時也帶來了更大的實現問題:棧上變數的地址可能會隨著棧的移動而發生變化。這直接帶來了CGO程式設計中,Go語言記憶體物件無法直接傳遞給C語言空間使用,因此後來Go語言官方針對CGO問題制定了複雜的記憶體使用規範。

總體來說,動態棧如何實現是一個如何取捨的問題,因為沒有銀彈、魚和熊掌不可兼得,目前的選擇是第一保證純Go程式的效能。

GC效能改進

Go語言是一個帶自動垃圾回收的語言(Garbage Collection ),簡稱GC(注意這是大寫的GC,小寫的gc表示Go語言的編譯器)。從Go語言誕生開始,GC的回收效能就是大家關注的熱點話題。

Go語言之所以能夠支援GC特性,是因為Go語言中每個變數都有完備的元資訊,透過這些元資訊可以很容易跟蹤全部指標的宣告週期。在Go1。4之前,GC採用的是STW停止世界的方式回收記憶體,停頓的時間經常是幾秒甚至達到幾十秒。因此早期社群有很多如何規避或降低GC操作的技巧文章。

第一次GC效能變革發生在Go1。5時期,這個時候Go語言的執行時和工具鏈已經全部從C語言改用Go語言實現,為GC程式碼的重構和最佳化提供了便利。Go1。5首次改用並行和增量的方式回收記憶體,這將GC挺短時間縮短到幾百毫秒。下圖是官網“Go GC: Latency Problem Solved”一文給出的資料:

Go 語言十年而立,Go2 蓄勢待發

Go1。5併發和增量的改進效果明顯,但是最重要的是為未來的改進奠定了基礎。在Go1。5之後的Go1。6版本中GC效能終於開始得到了徹底的提升:從Go1。6。0停頓時間降低到幾十毫秒,到Go1。6。3降低到了十毫秒以內。而Go1。6取得的成果在Go1。8的官方日誌得到證實:Go語言的GC通常低於100毫秒,甚至低於10毫秒!

當然,Go的GC最佳化的腳步不會停止,但是想再現Go1。5和Go1。6時那種激動人心的成果估計比較難了。在Go1。8之後的幾個版本中,官方的釋出日誌已經很少再出現量化的GC效能提升資料了。

Go語言自舉歷程

據說Go語言剛開始實現時是基於湯普森的C語言編譯改造而成,並且最開始輸出的是C語言程式碼(還沒有對外公開之前)。在開源之後到Go1。4之前,Go語言的編譯器和執行時都是採用C語言實現的。以至於早期可以用C語言實現一個Go語言函式!因為強烈依賴C語言工具鏈,因此Go1。4之前Go語言是完全不能自舉的。

從Go1。4開始,Go語言的執行時採用Go語言實現。具體實施的方式是Go團隊的rsc首先實現了一個簡化的C程式碼到Go程式碼的轉換工具,這個工具主要用於將之前C語言實現的Go語言執行時轉換為Go語言程式碼。因為是自動轉換的程式碼,因此可以得到比較可靠的Go程式碼。執行時轉換為Go語言實現之後,帶來的第一個好處就是GC可以精確知道每個記憶體指標的狀態(因為Go語言的變數有詳細的型別資訊),這也為Go1。5重寫GC提供了執行時基礎。

然後到了Go1。5,將編譯器也轉為Go語言實現。但是轉換到程式碼效能有一定的下降。很多程式的編譯時間甚至緩慢到幾十秒,這個時期網上出現了很多吐槽Go1。5編譯速度慢的問題。Go1。5採用Go語言編寫編譯器的同時,對工具鏈和目的碼都做了大量的重構工作。從Go1。5之後,交叉編譯變得異常簡單,只要GOOS=linux GOARCH=amd64 go build命令就可以從任何Go語言環境生成Linux/amd64的目的碼。

Go語言從Go1。4到Go1。5,經歷了兩個版本的演化終於實現了自舉的支援。當然自舉也會帶來一個哲學問題:Go語言的編譯器是否有後門?如果有後門的編譯器編譯出來的Go程式是否有後門?有後門的編譯器編譯出來的Go編譯器程式是否有後門?

失敗的嘗試

Go語言發展過程中也並不全是成功的案例,同時也存在一些失敗的嘗試。失敗乃成功之母,這些嘗試雖然最終失敗了,但是在嘗試的過程之中積累的經驗為新的方向提供了前進的動力。

因為Go語言的常量只支援數值和字串等少數幾個型別,早期的社群中一直呼籲為切片增加只讀型別。

為此rsc在開發分支首先試驗性地實現了該特性,但是在之後的實踐過程中又發現了和Go程式設計特性衝突的諸多問題,以至於在短暫的嘗試之後就放棄了只讀切片的特性。當然,初始化之後不能修改的變數特性依然是大家期望的一個特性(類似其它語言的final特性),希望在未來的Go2中能有一定的改善。

另一個嘗試是早期基於vendor的版本管理。

在Go1。5中首次引入vendor和internal特性,vendor用於打包外部第三方包,internal使用者保護內部的包。後來vendor被開源社群的各種版本管理工具所濫用,導致Go語言程式碼經常會出現一些不可構建的詭異問題。濫用vendor導致了vendor巢狀的問題,這和nodejs社群中node_modules目錄巢狀的問題類似。巢狀的vendor中最終會出現同一個包的不同版本,這根最後的稻草終於徹底擊潰了vendor機制,以至於Go語言官方團隊從頭開發了模組特性來徹底解決版本管理的問題。等到Go1。13模組化特性轉正之中,GOPATH和vendor等機制將被徹底淘汰。

Go語言作為一個開源專案,所有匯入的包必須有原始碼。一些號稱是商業使用者,呼籲Go語言支援二進位制包的匯入,這樣可以最大限度地保護商業程式碼。為了響應社群的需求,Go1。7增加了匯入二進位制包的功能。但是比較戲劇化的是,Go語言支援二進位制包匯入之後並沒有多少人在使用,甚至當初呼籲二進位制包的人也沒有使用(所以說很多社群的聲音未必能夠反映真實的需求)。為了一個沒有人使用的二進位制包特性,需要Go語言團隊投入相當的人力進行維護程式碼。為了減少這種不需要的特性,Go1。13將徹底關閉二進位制包的特性,從新輕裝上陣解決真實的需求。當然,Go語言也已經支援了生成靜態庫、共享庫和外掛的特性,也可以透過這些機制來保護程式碼。

失敗的嘗試可能還有一些,比如最近Go語言之父之一Robert Griesemer提交的透過try內建函式來簡化錯誤處理就被否決了。失敗的嘗試是一個好的現象,它表示Go語言依然在一些新興領域的嘗試——Go語言依然處於活躍期。

#FormatImgID_10#

o2的發展方向

Go語言原本就是短小精悍的語言,經過多年的發展Go1已經逼近穩定的極限。檢視官網的Talk頁面的報告數量可以發現,2015年之前是各種報告的巔峰,2016到2017年分享數量已經開始急劇下降,2018年至今已經沒有新的報告被收錄,這是因為該講的Go1語言特性早就被講過多次了。對於第一波Go語言愛好者來說也是如此,Go語言已經沒有什麼新的特性可以挖掘和學習了,或者說它已經不夠酷了。我們想Go語言官方團隊也是這樣的感覺,因此從2018年開始首先開始解決模組化的問題,然後開始正式討論Go2的新特性,並且從Go1。13重新啟動語言的進化。

模組化和構建管理有關係。在Go語言剛剛誕生之初,其實是透過一個Makefile目標進行構建。然後官方提供了go build命令構建,實現了零配置檔案構建,極大地簡化了構建的流程。再後來出現了go get命令,支援從網際網路上自動下載hg或git倉庫的程式碼進行構建,並同時引入GOPATH環境變數來防止非標準庫的程式碼。此後,第一波的版本管理工具也開始出現,透過動態調整GOPATH實現匯入特定版本的程式碼。隨後各種開源模仿、克隆的版本管理工具如雨後春筍般冒出來,基本都是模仿godeps的設計思路,基於GOPATH和後來的vendor來管理依賴包的版本,這也最終導致了vendor被過度濫用(前文已經講過vendor濫用帶來的問題)。最終在2018年,由rsc親自操刀從頭髮明瞭基於最小化版本依賴演算法的版本管理特性。模組化特性從Go1。11開始引入,將在Go1。13版本正式轉正,以後GOAPATH將徹底退出歷史舞臺。

因為rsc的工作直接宣判了開源社群的各種版本管理工具的死亡,這也導致了Go語言官方團隊和開源社群的諸多衝突和矛盾。在此需要補充說明下,Go語言的開發並不完全是開源陌生,Go語言的開源僅僅限於Issue的提交或BUG的修改,真正的語言設計始終走的是教堂元老會的模式。筆者以為這是最好的開源方式,很多開源社群的例子也說明了需要獨裁者的角色,而元老會正是這種角色。

在Go1。13中,除了模組化特性轉正之外,還有諸多語法的改進:比如十六進位制的浮點數、大的數字可以透過下劃線進行分隔、二進位制和八進位制的面值常量等。但是Go1。13還有一個重大的改進發生在errors標準庫中。errors庫增加了Is/As/Unwrap三個函式,這將用於支援錯誤的再次包裝和識別處理,是為了Go2中新的錯誤處理改進提前做準備。後續改進方向就是錯誤處理的控制流,之前已經出現用try/check關鍵字和try內建函式改進錯誤處理流程的提案,目前還沒有確定採用什麼方案。

Go2最期待的特性是泛型。

從開始Go語言官方明顯抵制泛型,到2018年開始公開討論泛型,讓泛型的愛好者看到了希望。很多人包括早期的Go官方都會說用介面模擬泛型,這其實只是一個藉口。泛型最大的問題不在於效能,而是隻有泛型才能夠為泛型容器或演算法提供一個型別安全的介面。比如一個Add(a, b T) T泛型函式是無法透過介面來實現對返回值型別的檢查的。如果Go語言支援了泛型,再結合Go語言組合語言支援的AVX512指令,可以期待Go語言將在CPU運算密集型領域佔有一席之地,甚至以後會出現純Go語言的機器學習演算法庫的實現。

最後一個值得關注的是Go語言對WebAssembly平臺的支援。

根據Ending定律:一切可編譯為WebAssembly的,終將會被編譯為WebAssembly。2018年,Fabrice Bellard大神基於WebAssembly技術,將Windows 2000作業系統搬到了瀏覽器環境執行。2019年出現了WebAssembly System Interface技術,這很可能是一個更輕量化的Docker替代技術。而Go語言也出現了一個變異版本TinyGo,目標就是為了更好地在WebAssembly或其它微控制器等受限環境執行Go程式。

#FormatImgID_11#

Go語言在中國

回想Go語言剛面世時的第一個例子,是列印“Hello, 世界”。只可惜這裡的“世界”並不是中文的“Hello, 世界”,而是日文的“Hello, 世界”。而日文還是基於中文漢字改造而來,這是整個中文世界的悲哀!

比較慶幸的是中國程式設計師比較給力,目前中國不僅僅是世界上Go語言關注度最高的國家,也是貢獻排名第二的國家。根據谷歌趨勢的資料,Go語言在中國的關注度佔全球的90%以上:

Go 語言十年而立,Go2 蓄勢待發

不僅僅是Go語言使用者,中國的Gopher對Go語言的貢獻也穩居美國之後。其中韋京光早在2010年就深度參與Go語言開發,將Go語言移植到Windows系統並實現了CGO支援。之後來自中國的Minux實現了iOS等諸多平臺的移植,並已經正式加入Go語言開發團隊。而目前Go語言中國貢獻者排名第一的是來自天津的史斌(benshi001),他的很多工作集中在編譯的最佳化方面,在全球Go語言貢獻者排名第39位。

最早Go語言中文愛好者都是透過谷歌討論組golang-china討論,目前該討論組還陸續會有新的文章釋出。然後到了2012年前後,因為諸多因素國內的討論開始集中到QQ群中(筆者在2010年建立了國內第一個Go語言QQ討論群)。再往後就是微信各種論壇遍地開花了。十年來,Go語言中文社群也一直非常活躍,社群人數穩步增長。這裡簡單回顧一下我知道的Go社群中的一些人和事。

Fango

如果在2010年關注Go語言,肯定會聽到Fango的名字。Fango是來自新加坡的Go語言愛好者,在Go語言剛面世不久他就寫了第一本(很可能是唯一一本)以Go語言為題材的小說《胡文·Go》,然後他還出版了第一本Go語言中文教材《Go語言·雲動力》。感謝Fango給大家帶來的精彩的Go語言故事。

許式偉和七牛雲

七牛是國內第一家大面積採用Go語言開發的公司,時間還在Go1。0正式釋出之前。許式偉也是大中華第一個知名的Go語言佈道師。許式偉和七牛雲在2012年也出版了一本《Go語言程式設計》教程,和Fango的圖書可能只差了一個多月的時間,編輯都是楊海鈴老師。其後七牛還有多本Go語言相關的專著或譯著,可以說在2015年之前,許式偉和七牛雲團隊絕對是國內Go語言社群推廣的主力。

筆者也在第一時間拜讀了《Go語言程式設計》一書,對其中如何實現介面和Goroutine排程的模擬依然印象深刻。感謝許式偉當時贈送的簽名版本《Go語言程式設計》,同時也感謝為我新出的《Go語言高階程式設計》寫序,謝謝許大!

Go 語言十年而立,Go2 蓄勢待發

Astaxie和GopherChina大會

對謝大最早的印象是在2012年前後,當時他開了一個免費的《Go Web程式設計》圖書,當前QQ群中很多小夥伴都參與審校(比如四月份平民、邊江和Oling Cat等)。Go Web程式設計是大家比較關注的方向,書中不僅僅講到了ORM的實現,還講到了beedb等元件。而beedb等這些元件最早演化成了Beego框架。根據前一段時間JetBrains展開的一個調查,Beego是Go語言三大流行的Web框架之一。

然後到了2015年,謝大正式開啟GopherChina大會的歷程。我雖然因為其它事情沒有現場參與,但是也預定了第一節GopherChina大會的會衫。然後在2018年終於以講師身份參加了上海的GopherChina大會,跟大家分享了CGO方向的技術,同時第一次見到謝大本尊。感謝謝大的GopherChina大會和《Go Web程式設計》!

其他人和專案

此外還有很多大家耳熟能詳的Go愛好者,比如《Learning Go》和Go Tour的中文翻譯者星星,建立了gogs的無聞,一種在翻譯Go官方文件的Oling Cat,雨痕的《Go語言學習筆記》對Go原始碼深度的解讀,建立了GoHackers的郝林等等。此外由國內的PingCAP公司主導開發的開源TiDB分散式資料庫也是一個極為著名的專案。感謝Go中國社群這些朋友和專案,是大家的努力帶來了Go語言在國內的繁榮。

#FormatImgID_14#

向Go語言學習

候傑老師曾經說過:勿在浮沙築高臺。而中國網際網路公司的繁榮更多是在業務層面,底層的基石軟體幾乎沒有一個是中國所創造。作為一個嚴肅的軟體開發人員,我們需要向Go語言學習,繼續紮實掌握底層的理論基礎,不能只聚焦於業務層面,否則下次中美貿易戰的時候依然要被西方卡脖子。

經過這麼多年發展,中國的軟體行業已經非常繁榮和成熟,同時很多軟體開發人員也開始進入35歲的中年門檻。其實35歲正是軟體開發人員第二次職業生涯的開始,是開始形成自我創造力的時候。但是某些資本家短視的996或007等急功近利的福報觀點正導致中國軟體人員過早進入未創新而衰的階段。中國的軟體工程師不應該是碼農、更不是碼畜牧,我們雖然不會喊口號但是始終在默默前行。

目前中國已經有大量的軟體開發人員有能力參與基礎軟體的設計和開發,正因為這一波腳踏實地程式開發人員的努力,我相信在下個十年我們可以Go得更遠。

作者:柴樹杉,國內第一批Go語言愛好者,Go程式碼貢獻者,同時也是《Go語言高階程式設計》和《WebAssembly標準入門》的作者。Github賬號為chai2010。

Go 語言十年而立,Go2 蓄勢待發

【END】

標簽: go  語言  Go1  GC  程式碼