爲何要用Go語言?

本文章創做於2020年4月,大約6000字,預計閱讀時間15分鐘,請坐和放寬。html

logo.png

前言

Go 是一個開源的編程語言,它能讓構造簡單、可靠且高效的軟件變得容易[1]。

Go 語言被設計成一門應用於搭載 Web 服務器,存儲集羣或相似用途的巨型中央服務器的系統編程語言。對於高性能分佈式系統領域而言,Go語言無疑比大多數其它語言有着更高的開發效率。它提供了海量並行的支持,這對於遊戲服務端的開發而言是再好不過了[1]。前端

其實早在2018年前,我就已經有在國內的程序員環境中斷斷續續地聽到Go語言的消息,Go語言提供的方便的併發編程方式,十分適合我當時選擇的畢業設計選題,可是受限於導師的語言選擇、項目的進度追趕、考研的時間壓榨,一直沒有機會來好好地學習這門語言。java

在進入研究生階段後,儘管研究的方向和算法相關,但將來的職業方向仍是選擇了之後端爲主,主要是由於想作更多和業務相關的工做。爲了能在有限的時間裏給予本身足夠深的知識底蘊,選擇了一些讓本身去深刻了解的方向,Go語言天然也在其中,今天終於有機會來開始研究這門語言。git

爲何要用Go語言?

撰寫此文的初衷,是本文的標題,也是我做爲初學者一直以來的疑問:程序員

「我爲何要用Go語言?」github

爲了回答這個問題,我翻閱了不少Go語言相關的文檔、書籍和教程,我發現我很難在它們之中找到很是明顯直接的答案,書上和教程只會說,「是的,Go語言好用」golang

對於部分人來講,這個問題的答案或許很「明顯」,好比選擇Go語言是由於Google設計的語言、Go開發賺的錢多、XX公司使用Go語言等等,若是想要了解這門語言更加本質的東西,僅僅這些答案我認爲是還不夠的。算法

部分Go的教徒可能會說,他們選擇的理由是和語言自己相關的,好比:編程

  • Go編譯快
  • Go執行快
  • Go併發編程方便
  • Go有垃圾回收(Garbage Collection, GC)

的確,Go是有這些特色,但這並不是都是Go獨有的後端

  • 運行時解釋的腳本語言(好比Python)幾乎不須要時間編譯
  • C、C++甚至是彙編,基本上可以榨乾一臺機器的大部分性能
  • 大部分語言都有併發編程的支持庫
  • 大部分語言都不須要程序員主動關注內存狀況

一些Go的忠實粉絲把這種All in One的特性做爲評價語言的標準,他們認爲至少在這些方面,Go是能夠完美的代替其餘語言的。

那麼,Go真的能優秀到徹底替代另外一個語言麼?

其實未必,我始終認爲銀彈是不存在的[2],不管是在此次調查前,仍是在此次調查後。

本文從Go語言被設計的初衷出發,深刻互聯網各類角落,調查Go所具備的那些特性是否足夠優秀,同時和其餘語言進行適當的比較,你能夠選擇性的閱讀、接受或者反對個人內容,畢竟有交流才能傳播知識。

個人最終目的是讓更多的初學者看到Go沒有輕易暴露出的缺點,同時也能看到Go真正優秀的地方

設計Go的初衷

Go語言的主要目標是將靜態語言的安全性和高效性與動態語言的易開發性進行有機結合,達到完美平衡,從而使編程變得更加有樂趣,而不是在艱難抉擇中痛苦前行[3]。

Google公司不可能平白無故地設計一個新語言(一些特性相比於其餘語言也沒有新到哪裏去),這一切確定是有緣由的。

設計Go語言是爲了解決當時Google開發遇到的一些問題[4]:

  • C++編譯慢、沒有現代化(入門級友好的)的內存管理
  • 數以萬計行的代碼,難以維護
  • 部署的平臺各式各樣,交叉編譯困難
  • ......

joke.png

找不到什麼合適的語言,想着反正都是弄來本身用,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-designers.png

Go 語言設計者:Griesemer、Thompson 和 Pike [3]

當時Google的不少工程師是用的都是C/C++,因此語法的設計上接近於C,Go的設計師們想要解決其餘語言使用中的缺點,可是仍保留他們的優勢[5]:

  • 靜態類型和運行時效率
  • 可讀性和易用性
  • 高性能的網絡和多進程
  • ...

emmm,這些聽起來仍是比較玄乎,畢竟設計歸設計,實現歸實現,咱們回顧一下如今Go的幾個主要特色,編譯速度、執行速度、內存管理以及併發編程。

Go的編譯爲何快

固然,設計Go語言也不是徹底從零開始,最初Go的團隊嘗試設計實現一個Go語言的編譯前端,由基於C的gcc編譯器來編譯成機器代碼,這個面向gcc的前端編譯器也就是目前的Go編譯器之一的gccgo。

與其說Go的編譯爲何快,不如先說說C++的編譯爲何慢,C++也能夠用gcc編譯,編譯速度的大部分差別頗有可能來源於語言設計自己。

在討論問題以前,其中須要先說明的一點是:這裏比較的編譯速度都是在靜態編譯下的

靜態編譯和動態編譯的區別:

  • 靜態編譯:編譯器在編譯可執行文件時,要把使用到的連接庫提取出來,連接打包進可執行文件中,編譯結果只有一個可執行文件。
  • 動態編譯:可執行文件須要附帶獨立的庫文件,不打包庫到可執行文件中,減小可執行文件體積,在執行的時候再調用庫便可。

兩種方式有各自的優勢和缺點,前者不須要去管理不一樣版本庫的兼容性問題,後者能夠減小內存和存儲的佔用(由於可讓不一樣程序共享同一個庫),兩種方式孰優孰弱,要對應到具體的工程問題上,Go默認的編譯方式是靜態編譯

回到咱們要討論的問題:C++的編譯爲何慢?

C++編譯慢的主要兩個大頭緣由[6]

  • 頭文件的include方式
  • 模板的編譯

C++使用include方式引用頭文件,會讓須要編譯的代碼有乘數級的增長,例如當同一個頭文件被同一個項目下的N個文件include時,編譯器會將頭文件引入到每一份代碼中,因此同一個頭文件會被編譯N次(這在大多數時候都是沒必要要的);C++使用的模板是爲了支持泛型編程,在編寫對不一樣類型的泛型函數時,能夠提供很大的便利,可是這對於編譯器來講,會增長很是多沒必要要的編譯負擔。

固然C++對這兩個問題有不少後續的優化方法,可是這對於不少開發者來講,他們不想在這上面有過多時間和精力開銷。

大部分後來的編程語言在引入文件的方式上,使用了import module來代替include 頭文件的方式,import解決了重複編譯的問題,固然Go也是使用的import方式;在模板的編譯問題上,因爲Go在設計理念上遵循從簡入手,因此沒有將泛函編程歸入到設計框架中,因此天生的沒有模版編譯帶來的時間開銷(沒有泛型支持也是不少人不滿Go語言的理由)。

在Go 的1.5 版本中,Go團隊使用Go語言來編寫Go語言的編譯器(也叫自舉),相比於gccgo來講:

  • 提升了編譯速度,但執行速度略有降低(性能細節優化還不如gcc)
  • 增長了可編譯的平臺類型(以往受限於gcc)

在此以外,Go語言語法中的關鍵字也是很是少的(Go1.11版本里只有25個)[7],這也能夠減小編譯器花費在語法解析上的時間開銷。

keywords.png

因此在我看來,Go編譯速度快,主要出於四個緣由

  • 使用了import的引用管理方式;
  • 沒有模板的編譯負擔;
  • 1.5版本後的自舉編譯器優化;
  • 更少的關鍵字。

因此爲了加快編譯速度、放棄C++而轉入Go的同時,也要考慮一下是否要放棄泛型編程的優勢。

注:泛型可能在Go 2版本得到支持。

Go的實際性能如何

Go的執行速度,能夠參考一個語言性能測試數據網站 —— The Computer Language Benchmarks Game[8]。

這個網站在不一樣的算法上對每一個語言進行測試,而後給出時間和內存上的開銷數據比對。

比較的語言有C++、Java、Python。

首先是時間開銷:

time-cost.png

注意時間開銷的單位是s,而且Y軸爲了方便進行不一樣跨度上的比較,因此選取的是對數軸(即非線性軸,爲1-10-100-1000的比較跨度)。

而後是內存開銷:

mem-cost.png

注意Y軸爲了方便進行不一樣跨度上的比較,因此選取的是對數軸(即非線性軸,爲1000-10000-100000-1000000的比較跨度)。

須要注意的是,語言自己的性能只決定了一個程序的最高理論性能,程序具體的性能還要取決於這個程序的實現方法,因此當各個語言的性能並無太大的差別時,性能每每只取決於程序實現的方式。

經過兩個圖的數據能夠分析:

  • Go雖然還沒法達到C++那樣的極致性能,可是在大部分狀況下已經很接近了
  • Go和Java在算法的時間開銷上難分伯仲,但在內存的開銷上Java就要高得多了;
  • Go在上述的絕大部分狀況下,至少時間和內存開銷都比Python要優秀得多;

Go的併發編程

Go的併發之因此比較受歡迎,網絡上的不少內容集中在幾個方面:

  • 天生併發的設計
  • 輕量化的併發編程方式
  • 較高的併發性能
  • 輕量級線程Goroutines、併發通訊Channels以及其餘便捷的併發同步控制工具

因爲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調度器的內容。

Go的垃圾回收

垃圾回收(英語:Garbage Collection,縮寫爲GC),在計算機科學中是一種自動的存儲器管理機制。當一個計算機上的動態存儲器再也不須要時,就應該予以釋放,以讓出存儲器,這種存儲器資源管理,稱爲垃圾回收。垃圾回收器可讓程序員減輕許多負擔,也減小程序員犯錯的機會[10]。

在使用Go或者其餘支持GC的語言時,不用再像C++同樣,手動地去釋放不須要的變量佔用的內容空間(free/delete)

的確,這很方便(對於懶人和容易忘記主動釋放的人),可是也多了一些限制(暗箱操做的不透明性以及在GC處理上的性能開銷)。GC也不是萬能的,當遇到一些對性能要求較高的場景,仍是須要記得進行一些主動釋放或優化操做(好比說自定義內存池)。

PS:將在將來的某一篇中會細緻地介紹Go垃圾回收的細節(若是大家也以爲有必要的話)。

何時能夠選擇Go?

Go有不少優勢,編譯快、性能好、天生併發以及垃圾回收,不少比較有特點的內容也尚未說到(好比gofmt)。

Go語言也有不少缺點,好比第三方庫支持還不夠多(相比於Python來講就少的太多了)、支持編譯的平臺還不夠廣、還有被稱爲噩夢的依賴版本管理(已經在改善了,可是尚未達到徹底可靠的程度)。

因此到底Go適合作什麼,不適合作什麼?

分析了這麼多後,這個問題其實很難回答,但咱們能夠選擇先從不適合的領域把Go剔除掉,看看咱們會剩下什麼。

Go不適合作什麼

  • 極致高性能優化的場景,你可能須要使用C/C++,甚至是彙編;
  • 簡單流程的腳本工具、數值分析、深度學習,可能Python更適合(至少目前是);
  • 搭一個博客或網站,PHP未嘗不是天下第一的語言呢;
  • 若是你想比較方便找到一份的後端工做,絕大部分公司的Java崗一直缺人(在實際生產過程當中,目前Go仍沒有比Java表現得好太多,至少沒有好到讓一個部門/公司將核心業務從新轉向Go來進行重構);
  • ...

你能夠找到相似上面那樣的不少場景,你可能會發現Go並不能那麼完美地替代掉誰。

Go適合作什麼

最後,到了咱們的終極問題,Go到底適合作什麼?

讀到這裏你可能會以爲,好像是我把Go的特性吹了一遍,而後忽然告訴你可能Go不適合你。

Go天生併發,面向併發,因此Go的定位一直很清楚,從最淺顯的視角來看,至少Go做爲一個有較高性能的併發後端來講,是具備很是大的誘惑力的。

尤爲對於後端相關的程序員而言,在某些業務功能的初步實現上,簡潔的語法、內置的併發、快速的編譯,均可以讓你更加高效快速地完成任務(前提是Go的內容足以完成你的任務),不用再去擔心編譯優化和內存回收、不用擔憂過多的時間和內存開銷、不用擔憂不一樣版本庫之間的衝突(靜態編譯)以及不用擔憂交叉編譯平臺適配問題。

大部分狀況下,編寫一個服務,你只須要:實現、編譯、部署、運行

高效快速,足夠敏捷,這在企業的絕大部分項目的初期都是適用的,這也是大部分項目對開發初期的要求。當一個項目或者服務真的能夠發展下去,需求的確觸碰到Go的天花板時,再考慮使用更加好的語言或方法去優化也爲時不晚。

簡而言之,儘管Go的過於簡潔帶來了不少問題(有些人說的難聽點叫過於簡單),Go所具備的優勢,可讓大部分人用編程語言這種工具,來解決對他們而言更加劇要的問題。

Go語言不是銀彈,但它的確能有效地解決這些問題。

參考文章

擴展閱讀

在調查Go的過程當中,發現了一些比較有意思、或者比較實用的文章,一併附在這裏。

  • 我爲何選擇使用 Go 語言?,該文寫於2016年,在個人文章基本構思完成的時候,偶然看到了這篇文章,做者有不少早期Go版本的開發經驗,裏面有更多的細節都是出自於工程師的經驗之談,我發現其中的部分想法和我不謀而合,你能夠把這篇文章看成本文的後續擴展閱讀,不過要注意文章的時效,可能說起到的一些Go的缺點如今已經被改進了。
  • C/C++編譯器的工做過程,主要是供不熟悉C系的朋友瞭解一下編譯器的工做過程。
  • The Computer Language Benchmarks Game,一個對各個語言進行性能測試的網站,裏面的算法具備必定的表明性,可是不能表明全部工程可能遇到的狀況,僅供參考。
  • 爲何 Go 語言在某些方面的性能還不如 Java?,這是知乎上一個2017年開始有的問題,你能夠看到不少人對於這個問題的分析,從多個角度來理解語言之間的性能差別。
  • go-wiki WhyGo,Go的Github倉庫上維護的Wiki中,有一篇關於WhyGo的文章整理,不過大部分是英文,裏面主要是不少關於「爲何我要選擇Go」的軟硬稿。
  • 爲何要使用Go語言,Go語言的優點在哪裏,這個知乎的提問更早,是來自2013年的Yvonne YU用戶,在Go的早期實際上是具備很大的爭議的,你能夠看到你們在各個問題上的博弈。
  • 哪些公司在使用Go,Go的Github倉庫上維護的Wiki中,有一篇關於全球都有哪些公司在使用Go,不過提供的信息大部分只有一個公司名,好比國內有阿里巴巴(而人家大部分都招Java),能夠看看但參考性不大。
  • Go 語言的優勢,缺點和使人厭惡的設計,這是Go語言中文網上一篇2018年的文章,若是你對語言自己的一些特性的設計感興趣,你能夠選擇看看,做者從不少語法層面上介紹了Go的優勢和缺點。
  • Ruby China - 瞎扯淡 真的不必浪費心思在 Go 語言上,這是我無心中找到的一篇有名的帖子,這個問題始於2013年,在Ruby China上,其中也是大佬們(可能)從各個角度來辯論Go是否值得學習,能夠看成武俠小說觀看。
  • The way to Go - 3.8 Go性能說明,《The way to Go》這本書上爲數很少關於Go性能問題的說明。
  • C++開發轉向go開發是不是一個好的發展方向?,2014年知乎上關於C++和Go的一個討論,其實我以爲「若是選擇一個並不意味着就要放棄另外一個」,程序員不是研究語言的,也不該該是隻靠某一門語言吃飯。
  • 我爲何放棄Go語言 Liigo,嗯,2014年,仍舊是Go爭議很大的時候,CSDN上一篇閱讀數很高的文章,做者從本身的角度對Go進行批判(Go早期的確是有很多問題),你能夠看到早期Go的不少問題,也能夠斟酌這些問題對你是否重要以及到底在2020年的Go中有沒有被解決。
  • Golang 自己是用什麼語言寫的?,一個關於編譯的有趣的問題,能夠適當瞭解。
  • 搞懂Go垃圾回收,一篇還算比較新的分析Go垃圾回收問題的文章。
  • 有趣的編程語言:Go 語言的啓動時間是 C 語言的 300 多倍,C# 的關鍵字最多,這篇InfoQ文章其實算是一個典型的標題黨,主要使用的是一個Github上關於各個語言HelloWorld程序啓動時間的測試數據(https://github.com/bdrung/sta...,使用gccgo編譯的Go程序的啓動時間很是地長,的確是C的300多倍,但使用GC編譯的Go程序啓動時間只是C的2倍。
  • Go 語言的歷史回顧,我一直在尋找一個整理Go的版本變更細節的文章,在Go的官方文檔和各類書籍上尋找無果時,在InfoQ上找到了一篇還算跟蹤地比較新的(Go 1.0 - Go 1.13)文章,對於初學者而言,知道語言的變化也是很重要的(好比方便的知道哪些問題解決了,哪些尚未被解決),可能以後會拓展性的寫一篇關於這個的文章。
相關文章
相關標籤/搜索