本文章創做於2020年4月,大約6000字,預計閱讀時間15分鐘,請坐和放寬。html
Go 是一個開源的編程語言,它能讓構造簡單、可靠且高效的軟件變得容易[1]。Go 語言被設計成一門應用於搭載 Web 服務器,存儲集羣或相似用途的巨型中央服務器的系統編程語言。對於高性能分佈式系統領域而言,Go語言無疑比大多數其它語言有着更高的開發效率。它提供了海量並行的支持,這對於遊戲服務端的開發而言是再好不過了[1]。前端
其實早在2018年前,我就已經有在國內的程序員環境中斷斷續續地聽到Go語言的消息,Go語言提供的方便的併發編程方式,十分適合我當時選擇的畢業設計選題,可是受限於導師的語言選擇、項目的進度追趕、考研的時間壓榨,一直沒有機會來好好地學習這門語言。java
在進入研究生階段後,儘管研究的方向和算法相關,但將來的職業方向仍是選擇了之後端爲主,主要是由於想作更多和業務相關的工做。爲了能在有限的時間裏給予本身足夠深的知識底蘊,選擇了一些讓本身去深刻了解的方向,Go語言天然也在其中,今天終於有機會來開始研究這門語言。git
撰寫此文的初衷,是本文的標題,也是我做爲初學者一直以來的疑問:程序員
「我爲何要用Go語言?」github
爲了回答這個問題,我翻閱了不少Go語言相關的文檔、書籍和教程,我發現我很難在它們之中找到很是明顯直接的答案,書上和教程只會說,「是的,Go語言好用」。golang
對於部分人來講,這個問題的答案或許很「明顯」,好比選擇Go語言是由於Google設計的語言、Go開發賺的錢多、XX公司使用Go語言等等,若是想要了解這門語言更加本質的東西,僅僅這些答案我認爲是還不夠的。算法
部分Go的教徒可能會說,他們選擇的理由是和語言自己相關的,好比:編程
的確,Go是有這些特色,但這並不是都是Go獨有的:後端
一些Go的忠實粉絲把這種All in One的特性做爲評價語言的標準,他們認爲至少在這些方面,Go是能夠完美的代替其餘語言的。
那麼,Go真的能優秀到徹底替代另外一個語言麼?
其實未必,我始終認爲銀彈是不存在的[2],不管是在此次調查前,仍是在此次調查後。
本文從Go語言被設計的初衷出發,深刻互聯網各類角落,調查Go所具備的那些特性是否足夠優秀,同時和其餘語言進行適當的比較,你能夠選擇性的閱讀、接受或者反對個人內容,畢竟有交流才能傳播知識。
個人最終目的是讓更多的初學者看到Go沒有輕易暴露出的缺點,同時也能看到Go真正優秀的地方。
Go語言的主要目標是將靜態語言的安全性和高效性與動態語言的易開發性進行有機結合,達到完美平衡,從而使編程變得更加有樂趣,而不是在艱難抉擇中痛苦前行[3]。
Google公司不可能平白無故地設計一個新語言(一些特性相比於其餘語言也沒有新到哪裏去),這一切確定是有緣由的。
設計Go語言是爲了解決當時Google開發遇到的一些問題[4]:
找不到什麼合適的語言,想着反正都是弄來本身用,Google選擇造個輪子試試。
Go 語言起源 2007 年,並於 2009 年正式對外發布。它從 2009 年 9 月 21 日開始做爲谷歌公司 20%兼職項目,即相關員工利用 20% 的空餘時間來參與 Go 語言的研發工做。該項目的三位領導者均是著名的 IT 工程師:Robert Griesemer,參與開發 Java HotSpot 虛擬機;Rob Pike,Go 語言項目總負責人,貝爾實驗室 Unix 團隊成員,參與的項目包括 Plan 9,Inferno 操做系統和 Limbo 編程語言;Ken Thompson,貝爾實驗室 Unix 團隊成員,C 語言、Unix 和 Plan 9 的創始人之一,與 Rob Pike 共同開發了 UTF-8 字符集規範。自 2008 年 1 月起,Ken Thompson 就開始研發一款以 C 語言爲目標結果的編譯器來拓展 Go 語言的設計思想[3]。
Go 語言設計者:Griesemer、Thompson 和 Pike [3]
當時Google的不少工程師是用的都是C/C++,因此語法的設計上接近於C,Go的設計師們想要解決其餘語言使用中的缺點,可是仍保留他們的優勢[5]:
emmm,這些聽起來仍是比較玄乎,畢竟設計歸設計,實現歸實現,咱們回顧一下如今Go的幾個主要特色,編譯速度、執行速度、內存管理以及併發編程。
固然,設計Go語言也不是徹底從零開始,最初Go的團隊嘗試設計實現一個Go語言的編譯前端,由基於C的gcc編譯器來編譯成機器代碼,這個面向gcc的前端編譯器也就是目前的Go編譯器之一的gccgo。
與其說Go的編譯爲何快,不如先說說C++的編譯爲何慢,C++也能夠用gcc編譯,編譯速度的大部分差別頗有可能來源於語言設計自己。
在討論問題以前,其中須要先說明的一點是:這裏比較的編譯速度都是在靜態編譯下的。
靜態編譯和動態編譯的區別:
兩種方式有各自的優勢和缺點,前者不須要去管理不一樣版本庫的兼容性問題,後者能夠減小內存和存儲的佔用(由於可讓不一樣程序共享同一個庫),兩種方式孰優孰弱,要對應到具體的工程問題上,Go默認的編譯方式是靜態編譯。
回到咱們要討論的問題:C++的編譯爲何慢?
C++編譯慢的主要兩個大頭緣由[6]:
C++使用include方式引用頭文件,會讓須要編譯的代碼有乘數級的增長,例如當同一個頭文件被同一個項目下的N個文件include時,編譯器會將頭文件引入到每一份代碼中,因此同一個頭文件會被編譯N次(這在大多數時候都是沒必要要的);C++使用的模板是爲了支持泛型編程,在編寫對不一樣類型的泛型函數時,能夠提供很大的便利,可是這對於編譯器來講,會增長很是多沒必要要的編譯負擔。
固然C++對這兩個問題有不少後續的優化方法,可是這對於不少開發者來講,他們不想在這上面有過多時間和精力開銷。
大部分後來的編程語言在引入文件的方式上,使用了import module來代替include 頭文件的方式,import解決了重複編譯的問題,固然Go也是使用的import方式;在模板的編譯問題上,因爲Go在設計理念上遵循從簡入手,因此沒有將泛函編程歸入到設計框架中,因此天生的沒有模版編譯帶來的時間開銷(沒有泛型支持也是不少人不滿Go語言的理由)。
在Go 的1.5 版本中,Go團隊使用Go語言來編寫Go語言的編譯器(也叫自舉),相比於gccgo來講:
在此以外,Go語言語法中的關鍵字也是很是少的(Go1.11版本里只有25個)[7],這也能夠減小編譯器花費在語法解析上的時間開銷。
因此在我看來,Go編譯速度快,主要出於四個緣由:
因此爲了加快編譯速度、放棄C++而轉入Go的同時,也要考慮一下是否要放棄泛型編程的優勢。
注:泛型可能在Go 2版本得到支持。
Go的執行速度,能夠參考一個語言性能測試數據網站 —— The Computer Language Benchmarks Game[8]。
這個網站在不一樣的算法上對每一個語言進行測試,而後給出時間和內存上的開銷數據比對。
比較的語言有C++、Java、Python。
首先是時間開銷:
注意:時間開銷的單位是s,而且Y軸爲了方便進行不一樣跨度上的比較,因此選取的是對數軸(即非線性軸,爲1-10-100-1000的比較跨度)。
而後是內存開銷:
注意:Y軸爲了方便進行不一樣跨度上的比較,因此選取的是對數軸(即非線性軸,爲1000-10000-100000-1000000的比較跨度)。
須要注意的是,語言自己的性能只決定了一個程序的最高理論性能,程序具體的性能還要取決於這個程序的實現方法,因此當各個語言的性能並無太大的差別時,性能每每只取決於程序實現的方式。
經過兩個圖的數據能夠分析:
Go的併發之因此比較受歡迎,網絡上的不少內容集中在幾個方面:
因爲Go在設計的時候就考慮到了併發的支持,或者說不少特性都是爲了併發而設計,這和一些後期庫支持併發和第三方庫支持併發的語言不一樣。
因此Go的併發到底有多方便?在Go中使用併發,只須要在普通的函數執行前加上一個go關鍵字,就能夠新建一個線程讓函數在其中執行:
func main() { go loop() // 啓動一個goroutine loop() }
這樣帶來的好處不只僅是讓併發編程更方便了,在一些特定狀況下,好比Go引用一些使用了併發的庫時,這些庫所使用的併發也是基於Go自己的併發設計,不會存在庫使用另外一套併發實現的狀況,這樣Go調度器在處理程序中的各類併發線程時,能夠有更加統一化的管理方式。
不過Go的併發對於程序的實現要求仍是比較高的,在使用一些通訊Channel的場合,稍有疏忽就可能出現死鎖的問題,好比:
fatal error: all goroutines are asleep - deadlock!
Go的併發量能夠比大部分語言裏普通的線程實現要高,這受益於輕量級的Goroutine,輕量化主要是它所佔用的空間要小得多,例如64位環境下的JVM,它會默認固定爲每一個線程分配1MB的線程棧空間,而Goroutines大概只有4-8KB,以後再按需分配。足夠輕量化的線程在相同的內存下也就能夠有更高併發量(服務器CPU尚未飽和的狀況下),同時也能夠減小不少上下文切換的時間開銷[9]。可是若是你的每一個線程佔用空間都很是大時(好比10MB,固然這是很是規需求的狀況下),Go的輕量化優點就沒有那麼明顯了。
Go在併發上的優勢很明顯,也是Go的功能目標,從語言設計上支持了併發,提供了統一便捷的工具,複雜的併發業務也須要在Go的一整套併發規範體系下進行編程,固然這確定會犧牲部分實現自由度,但能夠得到性能的提升和維護成本的降低。
PS:關於Go調度器的內容在這裏並無被說起,由於很難用簡單的文字向讀者說明該調度方式和其餘調度方式的優劣,將在將來的某一篇中會細緻地介紹Go調度器的內容。
垃圾回收(英語:Garbage Collection,縮寫爲GC),在計算機科學中是一種自動的存儲器管理機制。當一個計算機上的動態存儲器再也不須要時,就應該予以釋放,以讓出存儲器,這種存儲器資源管理,稱爲垃圾回收。垃圾回收器可讓程序員減輕許多負擔,也減小程序員犯錯的機會[10]。
在使用Go或者其餘支持GC的語言時,不用再像C++同樣,手動地去釋放不須要的變量佔用的內容空間(free/delete)。
的確,這很方便(對於懶人和容易忘記主動釋放的人),可是也多了一些限制(暗箱操做的不透明性以及在GC處理上的性能開銷)。GC也不是萬能的,當遇到一些對性能要求較高的場景,仍是須要記得進行一些主動釋放或優化操做(好比說自定義內存池)。
PS:將在將來的某一篇中會細緻地介紹Go垃圾回收的細節(若是大家也以爲有必要的話)。
Go有不少優勢,編譯快、性能好、天生併發以及垃圾回收,不少比較有特點的內容也尚未說到(好比gofmt)。
Go語言也有不少缺點,好比第三方庫支持還不夠多(相比於Python來講就少的太多了)、支持編譯的平臺還不夠廣、還有被稱爲噩夢的依賴版本管理(已經在改善了,可是尚未達到徹底可靠的程度)。
因此到底Go適合作什麼,不適合作什麼?
分析了這麼多後,這個問題其實很難回答,但咱們能夠選擇先從不適合的領域把Go剔除掉,看看咱們會剩下什麼。
你能夠找到相似上面那樣的不少場景,你可能會發現Go並不能那麼完美地替代掉誰。
最後,到了咱們的終極問題,Go到底適合作什麼?
讀到這裏你可能會以爲,好像是我把Go的特性吹了一遍,而後忽然告訴你可能Go不適合你。
Go天生併發,面向併發,因此Go的定位一直很清楚,從最淺顯的視角來看,至少Go做爲一個有較高性能的併發後端來講,是具備很是大的誘惑力的。
尤爲對於後端相關的程序員而言,在某些業務功能的初步實現上,簡潔的語法、內置的併發、快速的編譯,均可以讓你更加高效快速地完成任務(前提是Go的內容足以完成你的任務),不用再去擔心編譯優化和內存回收、不用擔憂過多的時間和內存開銷、不用擔憂不一樣版本庫之間的衝突(靜態編譯)以及不用擔憂交叉編譯平臺適配問題。
大部分狀況下,編寫一個服務,你只須要:實現、編譯、部署、運行。
高效快速,足夠敏捷,這在企業的絕大部分項目的初期都是適用的,這也是大部分項目對開發初期的要求。當一個項目或者服務真的能夠發展下去,需求的確觸碰到Go的天花板時,再考慮使用更加好的語言或方法去優化也爲時不晚。
簡而言之,儘管Go的過於簡潔帶來了不少問題(有些人說的難聽點叫過於簡單),Go所具備的優勢,可讓大部分人用編程語言這種工具,來解決對他們而言更加劇要的問題。
Go語言不是銀彈,但它的確能有效地解決這些問題。
在調查Go的過程當中,發現了一些比較有意思、或者比較實用的文章,一併附在這裏。