摘要:本文從Go的語法,類型系統,編碼風格,語言工具,編碼工具和使用案例等幾方面對Go語言進行了學習和探討。
Go語言發佈以後,不少公司特別是雲廠商也開始用Go語言重構產品的基礎架構,並且不少企業都是直接採用Go語言進行開發,最近熱火朝天的Docker就是採用Go語言進行開發的。本文咱們一塊兒來探討和學習一下Go語言的技術特色。先來看個例子:html
package main import ( "fmt" "time" ) // 要在goroutine中運行的函數。done通道將被用來通知工做已經完成。 func worker(done chan bool) { fmt.Print("working...") time.Sleep(time.Second) fmt.Println("done") // 通知完成。 done <- true } func main() { // 建立一個通道 done := make(chan bool, 1) go worker(done) // 等待done變爲true <-done }
上例中是一個在Go語言中使用goroutine和通道的例子。 其中:git
go 關鍵字是用來啓動一個goroutine程序員
done <- true, 向通道傳值github
<-done, 讀取通道值golang
Go是由RobertGriesemer、RobPike和KenThompson在Google設計的一種靜態類型化的、須編譯後才能運行的編程語言。web
Go在語法上相似於C語言,但它具備C語言沒有的優點,如內存安全、垃圾回收、結構化的類型和CSP風格的併發性。算法
它的域名是http://golang.org,因此一般被稱爲"Golang",但正確的名稱是Go。數據庫
Go的設計受C語言的影響,但更加簡單和安全。該語言包括以下特色:npm
Go的語法包含C語言中保持代碼簡潔性和可讀性的語法特色。編程
引入了一個聯合聲明/初始化操做符,容許程序員寫出i := 3或s :="Hello, world!",而不須要指定使用的變量類型。
這與C語言中的int i= 3; 和 const char *s = "Hello, world!";造成鮮明對比。
分號仍然是終止語句,但在行結束時是隱含的。
在Go中,一個函數方法能夠返回多個值,返回一個結果和錯誤err組合對是向調用者提示錯誤的常規方式。
Go的範圍表達式容許在數組、動態數組、字符串、字典和通道上進行簡潔的迭代,在C語言中,有三種循環來實現這個功能。
Go有許多內置的類型,包括數字類型(byte、int6四、float32等)、booleans和字符串(string)。
字符串是不可更改的。
內置的運算符和關鍵字(而不是函數)提供了串聯、比較和UTF-8編碼/解碼。
記錄類型能夠用struct關鍵字定義。
對於每一個類型T和每一個非負整數常數n,都有一個數組類型,表示爲[n]T,所以,不一樣長度的數組有不一樣的類型。
動態數組能夠做爲"Slice"使用,如對於某類型T,表示爲[]T。這些數組有一個長度和一個容量,容量規定了什麼時候須要分配新的內存來擴展數組。若干個Slice能夠共享它們的底層內存。
全部類型均可以定義指針, T類型的指針可定義爲T。地址抽取和隱式訪問使用&和操做符,這跟C語言同樣,或者隱式的經過方法調用或屬性訪問使用。
除了標準庫中的特殊的unsafe.Pointer類型,通常指針沒有指針運算。
對於一個組合對類型K、V,類型map[K]V是將類型K鍵映射到類型V值的哈希表的類型。
chan T是一個通道,容許在併發的Go進程之間發送T類型的值。
除了對接口的支持外,Go的類型系統是顯示的:類型關鍵字能夠用來定義一個新的命名類型,它與其餘具備相同佈局的命名類型(對於結構體來講,相同的成員按相同的順序排列)不一樣。類型之間的一些轉換(如各類整數類型之間的轉換)是預先定義好的,添加一個新的類型能夠定義額外的轉換,但命名類型之間的轉換必須始終顯式調用。例如,類型關鍵字能夠用來定義IPv4地址的類型,基於32位無符號整數:
type ipv4addr uint32
經過這個類型定義,ipv4addr(x)將uint32值x解釋爲IP地址。若是簡單地將x分配給類型爲ipv4addr的變量將會是一個類型錯誤。
常量表達式既能夠是類型化的,也能夠是 "非類型化的";若是它們所表明的值經過了編譯時的檢查,那麼當它們被分配給一個類型化的變量時,就會被賦予一個類型。
函數類型由func關鍵字表示;它們取0個或更多的參數並返回0個或更多的值,這些值都是要聲明類型的。
參數和返回值決定了一個函數的類型;好比,func(string, int32)(int, error)就是輸入一個字符串和一個32位有符號的整數,並返回一個有符號的整數和一個錯誤(內置接口類型)的值的函數類型。
任何命名的類型都有一個與之相關聯的方法集合。上面的IP地址例子能夠用一個檢查其值是否爲已知標準的方法來擴展:
// ZeroBroadcast報告addr是否爲255.255.255.255.255。 func (addr ipv4addr) ZeroBroadcast() bool { return addr == 0xFFFFFFFF }
以上的函數在ipv4addr上增長了一個方法,但這個方法在uint32上沒有。
Go提供了兩個功能來取代類繼承。
首先是嵌入方法,能夠當作是一種自動化的構成形式或委託代理。
第二種是接口,它提供了運行時的多態性。
接口是一類型,它在Go的類型系統中提供了一種有限的結構類型化形式。
一個接口類型的對象同時也有另外一種類型的定義對應,這點就像C++對象同時具備基類和派生類的特徵同樣。
Go接口是在Smalltalk編程語言的協議基礎上設計的。
在描述Go接口時使用了鴨式填充這個術語。
雖然鴨式填充這個術語沒有精確的定義,它一般是說這些對象的類型一致性沒有被靜態檢查。
因爲Go接口的一致性是由Go編譯器靜態地檢查的,因此Go的做者們更喜歡使用結構類型化這個詞。
接口類型的定義按名稱和類型列出了所需的方法。任何存在與接口類型I的所需方法匹配的函數的T類型的對象也是類型I的對象。類型T的定義不須要也不能識別類型I。例如,若是Shape、Square和Circle被定義爲:
import "math" type Shape interface { Area() float64 } type Square struct { // 注:沒有 "實現 "聲明 side float64 } func (sq Square) Area() float64 { return sq.side * sq.side } type Circle struct { // 這裏也沒有 "實現 "聲明 radius float64 } func (c Circle) Area() float64 { return math.Pi * math.Pow(c.radius, 2) }
一個正方形和一個圓都隱含着一個形狀(Shape)類型,而且能夠被分配給一個形狀(Shape)類型的變量。
Go的接口系統使用了告終構類型。接口也能夠嵌入其餘接口,其效果是建立一個組合接口,而這個組合接口正是由實現嵌入接口的類型和新定義的接口所增長的方法來知足的。
Go標準庫在多個地方使用接口來提供通用性,這包括基於Reader和Writer概念的輸入輸出系統。
除了經過接口調用方法,Go還容許經過運行時類型檢查將接口值轉換爲其餘類型。這就是類型斷言和類型切換。
空接口{}是一個重要的基本狀況,由於它能夠引用任何類型的選項。它相似於Java或C#中的Object類,能夠知足任何類型,包括像int這樣的內置類型。
使用空接口的代碼不能簡單地在被引用的對象上調用方法或內置操做符,但它能夠存儲interface{}值,經過類型斷言或類型切換嘗試將其轉換爲更有用的類型,或者用Go的reflect包來檢查它。
由於 interface{} 能夠引用任何值,因此它是一種擺脫靜態類型化限制的有效方式,就像C 語言中的 void*,但在運行時會有額外的類型檢查。
接口值是使用指向數據的指針和第二個指向運行時類型信息的指針來實現的。與Go中其餘一些使用指針實現的類型同樣,若是未初始化,接口值是零。
在Go的包系統中,每一個包都有一個路徑(如"compress/bzip2 "或"http://golang.org/x/net/html")和一個名稱(如bzip2或html)。
對其餘包的定義的引用必須始終以其餘包的名稱做爲前綴,而且只有其餘包的大寫的名稱才能被訪問:io.Reader是公開的,但bzip2.reader不是。
go get命令能夠檢索存儲在遠程資源庫中的包,鼓勵開發者在開發包時,在與源資源庫相對應的基礎路徑
(如http://example.com/user_name/...)內開發程序包,從而減小未來在標準庫或其餘外部庫中名稱碰撞的可能性。
有人提議Go引入一個合適的包管理解決方案,相似於CPANfor Perl或Rust的Cargo系統或Node的npm系統。
在計算機科學中,通訊順序過程(communicating sequential processes,CSP)是一種描述併發系統中交互模式的正式語言,它是併發數學理論家族中的一個成員,被稱爲過程算法(process algebras),或者說過程計算(process calculate),是基於消息的通道傳遞的數學理論。
CSP在設計Oceam編程語言時起了很大的影響,同時也影響了Limbo、RaftLib、Go、Crystal和Clojure的core.async等編程語言的設計。
CSP最先是由TonyHoare在1978年的一篇論文中描述的,後來有了很大的發展。
CSP做爲一種工具被實際應用於工業上,用於指定和驗證各類不一樣系統的併發功能,如T9000Transputer以及安全的電子商務系統。
CSP自己的理論目前也仍然是被積極研究的對象,包括增長其實際適用範圍的工做,如增長可分析的系統規模。
Go語言有內置的機制和庫支持來編寫併發程序。併發不只指的是CPU的並行性,還指的是異步性處理:讓相對慢的操做,如數據庫或網絡讀取等操做在作其餘工做的同時運行,這在基於事件的服務器中很常見。
主要的併發構造是goroutine,這是一種輕量級處理類型。一個以go關鍵字爲前綴的函數調用會在一個新的goroutine中啓動這個函數。
語言規範並無指定如何實現goroutine,但目前的實現將Go進程的goroutine複用到一個較小的操做系統線程集上,相似於Erlang中的調度。
雖然一個標準的庫包具備大多數經典的併發控制結構(mutex鎖等),但Go併發程序更偏重於通道,它提供了goroutines之間的消息傳功能。
可選的緩衝區以FIFO順序存儲消息,容許發送的goroutines在收到消息以前繼續進行。
通道是類型化的,因此chan T類型的通道只能用於傳輸T類型的消息。
特殊語法約定用於對它們進行操做;<-ch是一個表達式,它使執行中的goroutine在通道ch上阻塞,直到有一個值進來,而ch<- x則是發送值x(可能阻塞直到另外一個goroutine接收到這個值)。
內置的相似於開關的選擇語句能夠用來實現多通道上的非阻塞通訊。Go有一個內存模型,描述了goroutine必須如何使用通道或其餘操做來安全地共享數據。
通道的存在使Go有別於像Erlang這樣的actor模型式的併發語言,在這種語言中,消息是直接面向actor(對應於goroutine)的。在Go中,能夠經過在goroutine和通道之間保持一對一的對應關係來,Go語言也容許多個goroutine共享一個通道,或者一個goroutine在多個通道上發送和接收消息。
經過這些功能,人們能夠構建像workerpools、流水線(好比說,在下載文件時,對文件進行解壓縮和解析)、帶超時的後臺調用、對一組服務的"扇出"並行調用等併發構造。
通道也有一些超越進程間通訊的常規概念的用途,好比做爲一個併發安全的回收緩衝區列表,實現coroutines和實現迭代器。
Go的併發相關的結構約定(通道和替代通道輸入)來自於TonyHoare的通訊順序進程模型。
不像之前的併發編程語言,如Occam或Limbo(Go的共同設計者RobPike曾在此基礎上工做過的語言),Go沒有提供任何內置的安全或可驗證的併發概念。
雖然在Go中,上述的通訊處理模型是推薦使用的,但不是惟一的:一個程序中的全部goroutines共享一個單一的地址空間。這意味着可突變對象和指針能夠在goroutines之間共享。
有一項研究比較了一個不熟悉Go語言的老練程序員編寫的程序的大小(以代碼行數爲單位)和速度,以及一個Go專家(來自Google開發團隊)對這些程序的修正,對Chapel、Cilk和IntelTBB作了一樣的研究。
研究發現,非專家傾向於用每一個遞歸中的一條Go語句來寫分解-解決算法,而專家則用每一個處理器的一條Go語句來寫分佈式工做同步程序。Go專家的程序一般更快,但也更長。
Goroutine對於如何訪問共享數據沒有限制,這使得條件競賽成爲可能的問題。
具體來講,除非程序經過通道或其餘方式顯式同步,不然多個goroutine共享讀寫一個內存區域可能會發生問題。
此外,Go的內部數據結構,如接口值、動態數組頭、哈希表和字符串頭等內部數據結構也不能倖免於條件競賽,所以在多線程程序中,若是修改這些類型的共享實例沒有同步,就會存在影響類型和內存安全的狀況。
gc工具鏈中的連接器默認會建立靜態連接的二進制文件,所以全部的Go二進制文件都包括Go運行所須要的內容。
Go故意省略了其餘語言中常見的一些功能,包括繼承、通用編程、斷言、指針運算、隱式類型轉換、無標記的聯合和標記聯合。
Go做者在Go程序的風格方面付出了大量的努力:
主要的Go發行版包括構建、測試和分析代碼的工具。
go build,它只使用源文件中的信息來構建Go二進制文件,不使用單獨的makefiles。
gotest,用於單元測試和微基準
go fmt,用於格式化代碼
go get,用於檢索和安裝遠程包。
go vet,靜態分析器,查找代碼中的潛在錯誤。
go run,構建和執行代碼的快捷方式
godoc,用於顯示文檔或經過HTTP
gorename,用於以類型安全的方式重命名變量、函數等。
go generate,一個標準的調用代碼生成器的方法。
它還包括分析和調試支持、運行時診斷(例如,跟蹤垃圾收集暫停)和條件競賽測試器。
第三方工具的生態系統加強了標準的發佈系統,如:
gocode,它能夠在許多文本編輯器中自動完成代碼,
goimports(由Go團隊成員提供),它能夠根據須要自動添加/刪除包導入,以及errcheck,它能夠檢測可能無心中被忽略的錯誤代碼。
流行的Go代碼工具:
GoLand:JetBrains公司的IDE。
VisualStudio Code
LiteIDE:一個"簡單、開源、跨平臺的GoIDE"
Vim:用戶能夠安裝插件:
vim-go
用Go編寫的一些著名的開源應用包括:
Caddy,一個開源的HTTP/2web服務器,具備自動HTTPS功能。
CockroachDB,一個開源的、可生存的、強一致性、可擴展的SQL數據庫。
Docker,一套用於部署Linux容器的工具。
Ethereum,以太幣虛擬機區塊鏈的Go-Ethereum實現。
Hugo,一個靜態網站生成器
InfluxDB,一個專門用於處理高可用性和高性能要求的時間序列數據的開源數據庫。
InterPlanetaryFile System,一個可內容尋址、點對點的超媒體協議。
Juju,由UbuntuLinux的包裝商Canonical公司推出的服務協調工具。
Kubernetes容器管理系統
lnd,比特幣閃電網絡的實現。
Mattermost,一個團隊聊天系統
NATSMessaging,是一個開源的消息傳遞系統,其核心設計原則是性能、可擴展性和易用性。
OpenShift,雲計算服務平臺
Snappy,一個由Canonical開發的UbuntuTouch軟件包管理器。
Syncthing,一個開源的文件同步客戶端/服務器應用程序。
Terraform,是HashiCorp公司的一款開源的多雲基礎設施配置工具。
其餘使用Go的知名公司和網站包括:
Cacoo,使用Go和gRPC渲染用戶儀表板頁面和微服務。
Chango,程序化廣告公司,在其實時競價系統中使用Go。
CloudFoundry,平臺即服務系統
Cloudflare,三角編碼代理Railgun,分佈式DNS服務,以及密碼學、日誌、流處理和訪問SPDY網站的工具。
容器Linux(原CoreOS),是一個基於Linux的操做系統,使用Docker容器和rkt容器。
Couchbase、Couchbase服務器內的查詢和索引服務。
Dropbox,將部分關鍵組件從Python遷移到了Go。
谷歌,許多項目,特別是下載服務器http://dl.google.com。
Heroku,Doozer,一個提供鎖具服務的公司
HyperledgerFabric,一個開源的企業級分佈式分類帳項目。
MongoDB,管理MongoDB實例的工具。
Netflix的服務器架構的兩個部分。
Nutanix,用於其企業雲操做系統中的各類微服務。
Plug.dj,一個互動式在線社交音樂流媒體網站。
SendGrid是一家位於科羅拉多州博爾德市的事務性電子郵件發送和管理服務。
SoundCloud,"幾十個系統"
Splice,其在線音樂協做平臺的整個後端(API和解析器)。
ThoughtWorks,持續傳遞和即時信息的工具和應用(CoyIM)。
Twitch,他們基於IRC的聊天系統(從Python移植過來的)。
Uber,處理大量基於地理信息的查詢。
package main import "fmt" func main() { fmt.Println("Hello, world!") }
package main import ( "fmt" "time" ) func readword(ch chan string) { fmt.Println("Type a word, then hit Enter.") var word string fmt.Scanf("%s", &word) ch <- word } func timeout(t chan bool) { time.Sleep(5 * time.Second) t <- false } func main() { t := make(chan bool) go timeout(t) ch := make(chan string) go readword(ch) select { case word := <-ch: fmt.Println("Received", word) case <-t: fmt.Println("Timeout.") } }
沒有測試的代碼是不完整的,所以咱們須要看看代碼測試部分的編寫。
代碼:
func ExtractUsername(email string) string { at := strings.Index(email, "@") return email[:at] }
測試案例:
func TestExtractUsername(t *testing.T) { type args struct { email string } tests := []struct { name string args args want string }{ {"withoutDot", args{email: "r@google.com"}, "r"}, {"withDot", args{email: "jonh.smith@example.com"}, "jonh.smith"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := ExtractUsername(tt.args.email); got != tt.want { t.Errorf("ExtractUsername() = %v, want %v", got, tt.want) } }) } }
接下來我寫一個例子建立REST API後端服務:
咱們的服務提供以下的API:
### GET http://localhost:10000/ ### GET http://localhost:10000/all ### GET http://localhost:10000/article/1 ### POST http://localhost:10000/article HTTP/1.1 { "Id": "3", "Title": "Hello 2", "desc": "Article Description", "content": "Article Content" } ### PUT http://localhost:10000/article HTTP/1.1 { "Id": "2", "Title": "Hello 2 Update", "desc": "Article Description Update", "content": "Article Content Update" }
完整代碼:
package main import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "github.com/gorilla/mux" ) type Article struct { Id string `json:"Id"` Title string `json:"Title"` Desc string `json:"desc"` Content string `json:"content"` } var MapArticles map[string]Article var Articles []Article func returnAllArticles(w http.ResponseWriter, r *http.Request) { fmt.Println("Endpoint Hit: returnAllArticles") json.NewEncoder(w).Encode(Articles) } func homePage(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to the HomePage!") fmt.Println("Endpoint Hit: homePage") } func createNewArticle(w http.ResponseWriter, r *http.Request) { reqBody, _ := ioutil.ReadAll(r.Body) var article Article json.Unmarshal(reqBody, &article) Articles = append(Articles, article) MapArticles[article.Id] = article json.NewEncoder(w).Encode(article) } func updateArticle(w http.ResponseWriter, r *http.Request) { reqBody, _ := ioutil.ReadAll(r.Body) var article Article json.Unmarshal(reqBody, &article) found := false for index, v := range Articles { if v.Id == article.Id { // Found! found = true Articles[index] = article } } if !found { Articles = append(Articles, article) } MapArticles[article.Id] = article json.NewEncoder(w).Encode(article) } func returnSingleArticle(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) key := vars["id"] fmt.Fprintf(w, "Key: %s n", key) json.NewEncoder(w).Encode(MapArticles[key]) } func handleRequests() { myRouter := mux.NewRouter().StrictSlash(true) myRouter.HandleFunc("/", homePage) myRouter.HandleFunc("/all", returnAllArticles) myRouter.HandleFunc("/article", createNewArticle).Methods("POST") myRouter.HandleFunc("/article", updateArticle).Methods("PUT") myRouter.HandleFunc("/article/{id}", returnSingleArticle) log.Fatal(http.ListenAndServe(":10000", myRouter)) } func main() { fmt.Println("Rest API is ready ...") MapArticles = make(map[string]Article) Articles = []Article{ Article{Id: "1", Title: "Hello", Desc: "Article Description", Content: "Article Content"}, Article{Id: "2", Title: "Hello 2", Desc: "Article Description", Content: "Article Content"}, } for _, a := range Articles { MapArticles[a.Id] = a } handleRequests() }
調用添加,更新API之後返回全部數據的測試結果:
MicheleSimionato對Go大加讚賞:
接口系統簡潔,並刻意省略了繼承。
EngineYard的DaveAstels寫道:
Go是很是容易上手的。不多的基本語言概念,語法也很乾淨,設計得很清晰。Go目前仍是實驗性的,還有些地方比較粗糙。
2009年,Go被TIOBE編程社區指數評選爲年度最佳編程語言。
到2010年1月,Go的排名達到了第13位,超過了Pascal等成熟的語言。
但到了2015年6月,它的排名跌至第50位如下,低於COBOL和Fortran。
但截至2017年1月,它的排名又飆升至第13位,顯示出它的普及率和採用率有了顯著的增加。
Go被評爲2016年TIOBE年度最佳編程語言。
BruceEckel曾表示:
C++的複雜性(在新的C++中甚至增長了更多的複雜性),以及由此帶來的對生產力的影響,已經沒有任何理由繼續使用C++了。C++程序員爲了克服C語言的一些問題而作出的加強初衷目前已經沒有了意義,而Go此時顯得更有意義。
2011年一位Google工程師R.Hundt對Go語言及其GC實現與C++(GCC)、Java和Scala的對比評估發現。
Go提供了有趣的語言特性,這也使得Go語言有了簡潔、標準化的特徵。這種語言的編譯器還不成熟,這在性能和二進制大小上都有體現。
這一評價收到了Go開發團隊的快速反應。
IanLance Taylor由於Hundt的評論改進了Go代碼;
RussCox隨後對Go代碼以及C++代碼進行了優化,並讓Go代碼的運行速度比C++略快,比評論中使用的代碼性能快了一個數量級以上。
2009年11月10日,也就是Go!編程語言全面發佈的當天,Go!編程語言的開發者FrancisMcCabe(注意是感嘆號)要求更改Google的語言名稱,以免與他花了10年時間開發的語言混淆。
McCabe表示了對谷歌這個'大塊頭'最終會碾壓他"的擔心,這種擔心引發了120多名開發者的共鳴,他們在Google官方的問題線程上評論說他們應該更名,有些人甚至說這個問題違背了Google的座右銘:"不要做惡。"
2010年10月12日,谷歌開發者RussCox關閉了這個問題,自定義狀態爲"不幸",並附上了如下評論:
"有不少計算產品和服務都被命名爲Go。在咱們發佈以來的11個月裏,這兩種語言的混淆度極低。"
Go的批評家們的觀點:
本文從Go的語法,類型系統,編碼風格,語言工具,編碼工具和使用案例等幾方面對Go語言進行了學習和探討,但願能夠拋磚引玉,對Go語言感興趣的同仁有所裨益。