2015年,整個IT技術領域發生了許多深刻而又複雜的變化,InfoQ策劃了「解讀2015」年終技術盤點系列文章,希望能夠給讀者清晰地梳理出技術領域在這一年的發展變化,回顧過去,繼續前行。讀者可參照專欄去深入Go語言,也可參照國內Go項目匯總和Go命令教程學習Go語言。
現今,21世紀的第2個十年已經過半,網際網路也真正進入了極速發展的階段。在國內, 大家已經對「雲計算」和「大數據」等名詞耳熟能詳了。在網際網路軟體開發領域,最主流或火爆的技術也無不與之有關。就拿Golang(也可稱為Go語言)來說,它就號稱「雲計算時代的C語言」。Go語言在軟體開發效率和運行效率之間做出了絕佳的權衡。這使得它既適應於網際網路應用的快速開發,又能在高並發、高性能的開發場景中如魚得水。
在2015年,Go語言在服務端程序開發和Web開發領域大放光彩的同時也成功介入到了移動端開發領域。也正因為此,越來越多的創業公司(尤其是網際網路應用和雲計算領域的創業公司)選擇Go語言作為其技術棧的重要組成部分。在國內,據不完全統計,已在生產環境中使用Go語言程序的知名網際網路公司已有百度、美團、360、京東、搜狐、豌豆莢、宜信、微影時代等等,更不用說國內日漸增多的雲計算創業公司了。由此可見,對於廣大的網際網路軟體開發者而言,關注和學習Go語言已經是已經很有必要的事情了。
2016年已經到來,距Google在2012年3月發布的Go 1.0已將近4年。在2015年,Go語言發生了不小的變化。從該年初發布的1.4版本到該年8月下旬發布的1.5版本,Go語言終於完成了自舉的過程,即:幾乎完全用Go語言程序重寫了自己,僅留有少許彙編程序。
Go語言的自舉非常徹底,包括了最核心的編譯器、連結器、運行時系統等。顯然,這是一個很有意義的過程,代表著能力和自信。與此同時,Go語言的運行時性能得到了大幅提升,尤其是在1.5版本完成的並發GC使得Go語言程序在響應時間方面有了質的飛躍。另外,Go語言所支持的作業系統和計算架構越來越多,幾乎涵蓋了現今主流甚至非主流的所有選項。
當然,改變不止如此。下面,我們就列舉幾個比較驚豔的改變,並稍加剖析。
並發的GC
GC,一般認為是garbage collector的縮寫形式,通常被譯為垃圾回收器。不過,它有時候也被看做是garbage collection的縮寫形式,中文譯為垃圾回收。在下文中,當GC被當做動詞用時,指的是垃圾回收。但當GC被當做名詞用時,指的是垃圾回收器。
在1.4以及之前版本的Go語言中,每次GC都會導致完全的「stop the world」(也可稱之為STW)。這意味著在GC期間,Go語言的運行時系統會讓調度器暫停對已啟用的Goroutine的一切調度。也就是說,任何未處於運行狀態的Goroutine都不會被遞交至內核線程和運行,直到當次GC完成。如此暫停的代價不容忽視,對於有高並發需求的程序來說有時會顯得非常棘手。在1.5版本出來之前,Go語言的GC也常常因此被開發者們詬病。
Go 1.5的GC是一個非分代、無轉移的採用標記-清掃算法和三色標記法的並發垃圾回收器。它大刀闊斧地利用各種手段大大縮減了STW的時間。Go語言官方保證,在50毫秒的Go程序運行時間中因GC導致的調度停頓至多只有10毫秒。有了這一保證,相當於為Go程序設定了一個響應時間的上限。對於對響應時間敏感的程序(許多網際網路程序都是如此),這絕對是一個重大利好。當然,如此質的飛躍並不是一蹴而就的。實際上,Go 1.4也為此做了很多鋪墊,比如對Goroutine棧的改造以完全保證GC的標記操作的準確無誤。圖1展示了在最近幾個版本的Go語言中GC的STW時間與內存堆大小的對應關係。
圖1 GC Pause vs. Heap Size
總之,Go 1.5為Go程序開啟了全並發的時代。雖然Go語言官方說當前的GC還可以被進一步優化,但是筆者認為它已不再會成為Go程序性能的瓶頸。
Go語言的亮點之一就是自帶了很多標準工具以幫助開發人員方便地進行Go程序的檢查、格式化、編譯、測試、部署,甚至升級。這些工具已經涵蓋了一個軟體的生命周期的方方面面,極大的方便了Go程序的開發者們。在1.4版本中,Go語言的標準工具集中加入了go generate。顧名思義,這是一個用於生成Go語言代碼的命令。有意思的是,這源於一個幾乎所有的電腦程式研發者們都有過的夢想——讓電腦程式自己編寫程序。go generate命令可以利用YACC(Yet Another Compiler Compiler,一種編譯器的生成器)並根據某種描述文件來生成Go語言代碼。
不過,千萬不要被這句話嚇到。即使我們不懂YACC,甚至對Go語言的AST(Abstract Syntax Tree,譯作抽象語法樹)一無所知,也可以使用go generate命令。比如,我們可以利用go generate命令把一些HTML(Hypertext Markup Language,譯作超文本標記語言)頁面模板文件內置到生成的Go程序代碼文件中(順便說一句,Go語言有自己的HTML頁面模板語法,可用於編寫HTML頁面模板)。這樣就無需在部署用於Web站點的Go程序時攜帶那些額外的文件了。下面展示一小段用於實現此功能的代碼:
程式設計師們應該可以猜到最後一行代碼實際上是一行注釋。實際上,只要有了這行注釋(其中的tpl_loader是筆者寫的一個小工具,也非常的簡單易懂),再在當前代碼包目錄下運行go generate命令,就可以實現上述功能了。在Go語言官方博客中可看到更詳細的說明。
在2015年裡,Go語言在標準工具方面的增進還不止於此。一個更加令人興奮的標準工具——go tool trace——隨著1.5版本的發布而到來。Go程序開發者們可以利用這一工具來圖形化的展示出Go程序的追蹤文件。當然,在展示之前,我們先要通過某種方式生成這樣的文件。Go語言為我們提供了三種用於生成Go程序追蹤文件的方法:通過顯式調用指定的標準庫函數以手動生成、導入指定的標準庫代碼包以使其自動生成,以及在運行程序測試時添加指定標記來生成。此後,當我們運行go tool trace命令並把相應的可執行的Go程序文件和Go程序追蹤文件作為參數以後,就可以在你的默認Web瀏覽器中看到類似下圖的圖形化展示了。
圖2 Go程序追蹤文件的圖形化展示
如圖所示,Go程序追蹤文件可以呈現出對應Go程序的內存使用、Goroutine和內核線程狀態、調度過程等信息。為我們調試Go並發程序提供了非常有力的輔助。
除了上述兩個新增的標準工具之外,Go語言官方也對一些已有的標準工具做了改進,比如:為go build命令和go test命令新增了可用標記以使其更加靈活、增強了go tool vet命令和go doc命令的功能,等等。
Go語言對代碼包的訪問控制的進一步增強始於1.4版本。在1.4版本之前,Go語言對程序實體(包括變量、常量、類型聲明、函數等)採取的是兩級訪問控制策略。程序實體的名稱的第一個字母的大小寫決定了它的可訪問範圍。若為大寫,則該程序實體可以被存在於任何位置的代碼訪問到。我們稱這樣的程序實體是公開的。若為小寫,則該程序實體僅能被存在於當前代碼包的代碼訪問到。我們稱這樣的程序實體是包級私有的。這樣簡明扼要的規則非常容易被記住。
到了Go 1.4時代,第三種訪問控制規則出現。官方稱之為Internalpackages。其含義是,如果代碼包A直接包含了一個名為internal的子代碼包,那麼這個internal包中公開的程序實體僅能被存在於代碼包A及其子代碼包中的代碼訪問到。這相當於使Go程序代碼有了「模塊級」的訪問控制。
不過,在Go 1.4中,這個「模塊級」的訪問控制只是對Go語言源碼和標準庫中的(即<Go語言安裝目錄>/src中的)代碼包有效,而對於其它代碼包則是無效的。也就是說,這是一個過渡階段。為的是讓廣大Go程序開發者有個適應期。到了Go 1.5,由internal代碼包代表的「模塊級私有」訪問控制規則才對所有代碼包有了強制約束力。至此,Go語言擁有了一套完整的三層訪問控制策略。相比於之前的兩層訪問控制策略,它更加靈活和完善。
在1.5版本之前,我們要想實現Go語言程序的跨平臺編譯是相當困難的。雖然因此催生出了幾個開源的輔助工具,但其步驟也依然是相當繁瑣的。其最主要的原因是那時的Go語言編譯工具是由C語言編寫的,是平臺相關的。這裡的平臺相關,是指被編譯後的程序的運行必要條件包含了目標計算機(也就是用於運行該程序的那臺計算機)的作業系統和計算架構。
其中,作業系統的可選項有windows、linux、darwin等,而計算架構的可選項目前有386(即32位計算架構)、amd64(即64位計算架構)和arm(一種基於精簡指令集的計算架構,多用於便攜設備專用CPU)。例如,我們在32位的Windows作業系統下,使用平臺相關的Go語言編譯工具編譯的程序是不能在64位的Linux作業系統下運行的,反之亦然。
我們已經知道,Go 1.5的所有編譯工具和運行時系統都被用Go語言重寫了。這使得這些編譯工具被進行了一系列合併,並且跨越了平臺的界線。例如,之前針對不同計算架構的Go語言編輯器5g、6g和8g被合而為一併命名為compile。隨之而來的優勢就是,我們可以使用這些編譯工具輕而易舉的進行跨平臺的程序編譯操作。
我們只需在程序編譯操作之前設置一下目標計算的作業系統和計算架構。前者通過設置環境變量GOOS來實現,而後者通過設置環境變量GOARCH來實現。例如,當我在64位的Linux作業系統下通過執行如下命令來編譯一個Go源碼文件之後,就可以在32位的Windows作業系統下直接運行那個編譯後的結果文件了。
從Go 1.4開始,開發人員已經可以利用官方擴展庫中的mobile代碼包來編寫Android App了。到了Go 1.5,其對iOS App的支持也已可用。這無疑是一大裡程碑,使人振奮!它使得Go語言的適用領域大大拓展,並壘砌了其在移動網際網路時代甚至物聯網時代的根基。開發者們既可以使用Go語言來構建原生的Android App或iOS App,也可以用它來編寫可被App程序調用的基礎庫。
讀者可以通過官方文檔來了解相關步驟,也可以去看看Go語言的創始人之一Rob Pike為大家編寫的示例App。另外,大家也可以關注Go語言北京用戶組和北京GDG( Google Developer Group - Beijing)聯合主辦的Go語言技術聚會,其中會包括與此有關的Topic。此聚會的時間初定在2016年2月的下旬。
除上述比較突出的變化之外,Go語言在很多地方也做了調整。比如,Goroutine內存棧的增長方式的變更,Goroutine內存棧的初始大小由8K縮減為了2K、GOMAXPROCS的默認值由1變成與當前計算機的CPU核心數一致、Go代碼可以被用於生成動態連結庫了,等等。對於這些調整,筆者就不一一細說了。不過,它們對於Go語言在2015年的精進也都起到了一定的推動作用。
總之,Go語言在2015年的發展迅速且振奮人心。無論在其本身的功能、性能和適用領域上,還是在社區方面(尤其是在中國)都是如此。如果說筆者在著《Go並發編程實戰》這本書的時候還只是建議大家把Go語言作為自己的第一或第二程式語言並以此作為長線技術投資的話,那麼現在我強烈建議所有網際網路軟體開發者都去嘗試並使用Go語言構建他們的(個人或公司的)軟體系統,並真正將其作為手邊的常用工具。
Go語言號稱是雲計算時代的C語言。它也正在持續、快速地向著這一目標前進。如果你也打算跟上後雲計算時代、物聯網時代以及不久就會出現的人工智慧時代的話,那麼就很有必要玩兒轉Go語言了。我相信,它一定不會讓你失望。
在2016年,我們目前可見的Go語言進展就是預計在2月份發布的1.6版本了。在這個版本中,Go語言官方準備重點發展UI庫。這也是Go語言當前的一大短板。在官方的UI庫完全就緒之前,筆者希望大家去關注一下七葉(優秀的國產Go語言IDE——LiteIDE——的作者)的新項目GoQt。此項目就是一個基於QT的Go語言UI庫。鑑於LiteIDE的優秀,我非常看好這個項目。
另外,Go語言在程序測試支持、程序運行分析以及程序調試方面都會有所改進,尤其是後者。實際上,許多不適應使用Go語言開發程序的程式設計師的最大抱怨就是Go語言程序不易調試(不過大家可以去了解下真正的Go語言愛好者是怎樣調試Go程序的)。Go語言官方也在2015年的重大版本升級中對這一方面做出了很多改進。隨之而來的就是Go語言在於各大主流IDE的集成方面所作出的不斷努力。在Go 1.6,這種努力還會繼續。
程序的開發效率與運行效率同樣重要。甚至在某些時候,前者比後者更加重要。這也是許多腳本語言得以生存並繁榮發展的重要原因之一。Go語言的創造者們更是深諳此道。最後,Go語言還會在移動App開發方面進行一步的增強。筆者相信Go語言在這一開發領域一定會有長足的進步的。
在奇點臨近的當下,由於各種智能行動裝置的先天優勢,移動開發需求持續暴漲。我想,大多數網際網路軟體開發團隊(尤其是創業團隊)都會想用儘量精簡的技術棧去支持多方產品的需求。如果能用一種既可快速上手又能高效運維的技術那該有多好。如果你通讀本篇並有所思考,就很可能有與我相同的感受——Go語言就是這樣的一門技術。如果你覺得理由並不充分或想深入了解Go語言,那麼就加入到Go語言社區並切實的感受一下吧。筆者發起的Go語言北京用戶組(微信公眾號:golang-beijing)也歡迎你的加入。
在不久的將來,Go語言一定會實現在程序開發領域的全面覆蓋。到那時,Go程式設計師的含金量也就毋庸置疑了。那麼,我們為什麼不現在就去做如此重要的技術投資或去積極的在內部培養Go開發工程師呢?
作者簡介:郝林,Go語言北京用戶組的發起人,著有圖靈原創圖書《Go並發編程實戰》,同時也是在線免費教程《Go命令教程》和《Go語言第一課》的作者。現在微賽時代擔任平臺研發負責人。