這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
由於前一階段實習中接到的項目的告一段落,不知不覺便多出了許多空餘的時間,於是就想總結一下最近因為個人興趣而學習的一些東西。從這篇文章開始以及後面陸續的幾篇關於GO語言的文章,均是博主最近對GO語言學習過程中的一些感悟、總結,類似於學習筆記的東西。記錄下來並整理成部落格一為對學習的知識做一個整理,二為分享出來給大家(因為國內關於GO語言的中文資料比較少),由於博主能力和知識有限,難免有所靡誤,還望勘正。
由於Go最近一系列出色的表現,從一開始Go便緊緊地吸引住了我的眼球。類似於Erlang、Scala等語言,Go也是天生為並發而設計的語言,Go有著許多在原生層面對並發編程進行支援的優秀特性,比如大名鼎鼎的Goroutines、Channels、Select等原生特性。那麼廢話不多說,這一篇主要是對GO語言中的並發編程模式做一個粗略的歸納總結,文中樣本參考自golang conference中的一些演講和部落格,涉及到的Go語言的文法知識細節將予以略去。Go語言文法請參考http://golang.org/
幾點強調之處
1. 並發而非並行
首先我們要明確兩個名詞:並發(Concurrency)、並行(Parallelism)。這兩個詞可能大家經常搞混淆,因為這兩個詞所標書的意思太過相近,但是前者更加偏向於設計(Design),而後者更加偏向於結構(Structure)。
2. 什麼是Goroutines
Goroutine是一個通過go關鍵字起起來的獨立的執行某個function的過程,它擁有獨立的可以自行管理的調用棧。
goroutine非常廉價,你可以擁有幾千甚至上萬的goroutines
goroutine不是thread
一個thread之下可能有上千的goroutines
你可以把goroutine理解為廉價的thread
讓我們從幾個例子開始
func boring(msg string) { for i := 0; ; i++ { fmt.Println(msg, i) time.Sleep(time.Second) }}
顯而易見,這個函數永不停歇的列印msg字串,並且迴圈中間會sleep一秒鐘,接下來讓我們不斷改進這個函數。
func boring(msg string) { for i := 0; ; i++ { fmt.Println(msg, i) time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) }}
我們看到這個函數不再是sleep固定的時間,而是rand出一個隨機的duration。這樣,可以讓我們的這個無聊的函數稍微不可預期一點。
func main() { boring("boring!")}func boring(msg string) { for i := 0; ; i++ { fmt.Println(msg, i) time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) }}
好了,無聊的函數跑起來了~~但是,目前我們還沒有用到Goroutines的特性
package mainimport ( "fmt" "math/rand" "time")func main() {go boring("boring!")}
程式的輸出為:
[no output]Program exited.
納尼??!!奇怪啊,為什麼程式沒有輸出捏?其實真相是,main函數在開啟boring方法的新的goroutine之後,沒有等待boring方法調用fmt.Println就急急忙忙返回退出了。當main退出之時,我們的程式自然而然也就退出了。
func main() {go boring("boring!")fmt.Println("I'm listening.")time.Sleep(2 * time.Second)fmt.Println("You're boring; I'm leaving.")}
現在我們就可以在主程式退出之前看到boring函數輸出的message了。但是等等,我們現在還只是一個goroutine,還沒有涉及到真正意義上的並發。
讓我們先來看一個簡單的使用channels進行同步的例子
var syn chan int = make(chan int)func foo(){for(i := 0; i < 5; i++){fmt.Println("i am running!")}syn <- 1}func main(){go foo()<- syn}
很簡單吧,通過使用通道syn,可以進行簡單的同步。這樣,在main函數退出之間首先會在讀取syn處阻塞,除非foo向syn寫入資料。
func boring(msg string, c chan string) { for i := 0; ; i++ { c <- fmt.Sprintf("%s %d", msg, i) time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)}}func main() {c := make(chan string)go boring("boring!", c) for i := 0; i < 5; i++ { fmt.Printf("You say: %q\n", <-c) } fmt.Println("You're boring; I'm leaving.")}
我們通過channel將main和boring聯絡起來,從而讓本來毫無關係的他們能夠自然地交流,從而知曉彼此的狀態。上面程式便是通過channel來進行的同步。當main函數執行 "<- c"時會發生阻塞,除非boring中執行"c <- fmt.Sprintf("%s %d", msg, i)"向通道中寫入資料才會解除阻塞。由此觀之,即針對同一個channel,sender和receiver必須要一個讀一個寫才能使得channel暢通不阻塞。如此一來,便可以通過channel進行交流和同步。