因爲前一階段實習中接到的項目的告一段落,不知不覺便多出了許多空餘的時間,因而就想總結一下最近由於我的興趣而學習的一些東西。從這篇文章開始以及後面陸續的幾篇關於GO語言的文章,均是博主最近對GO語言學習過程當中的一些感悟、總結,相似於學習筆記的東西。記錄下來並整理成博客一爲對學習的知識作一個整理,二爲分享出來給你們(由於國內關於GO語言的中文資料比較少),因爲博主能力和知識有限,不免有所靡誤,還望勘正。
golang
因爲Go最近一系列出色的表現,從一開始Go便牢牢地吸引住了個人眼球。相似於Erlang、Scala等語言,Go也是天生爲併發而設計的語言,Go有着許多在原生層面對併發編程進行支持的優秀特性,好比大名鼎鼎的Goroutines、Channels、Select等原生特性。那麼廢話很少說,這一篇主要是對GO語言中的併發編程模式作一個粗略的概括總結,文中示例參考自golang conference中的一些演講和博客,涉及到的Go語言的語法知識細節將予以略去。Go語言語法請參考http://golang.org/shell
首先咱們要明確兩個名詞:併發(Concurrency)、並行(Parallelism)。這兩個詞可能你們常常搞混淆,由於這兩個詞所標書的意思太過相近,可是前者更加偏向於設計(Design),然後者更加偏向於結構(Structure)。編程
若是你有隻有一個CPU,那麼你的程序能夠是併發的,但必定不是並行的併發
一個良好的併發程序並不是必定是並行的函數
並行是一種物理狀態,而併發是一種設計思想、程序的內部結構學習
多處理器纔有可能達到併發的物理狀態spa
goroutine很是廉價,你能夠擁有幾千甚至上萬的goroutines設計
goroutine不是threadcode
一個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 main import ( "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進行交流和同步。