這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
並行與並發
理論式的概念:
並行:多件事在同一時刻發生。
並發:多件事在同一時間間隔發生。
5歲小孩都能看懂的解釋:
摘自:http://www.cnblogs.com/yangecnu/p/3164167.html 和 Concurrent and Parallel Programming
上文如果用程式員的語言來講,CPU處理器相當於的咖啡機的角色,任務相當於隊列中的人。
並發與並行的區別:
一定要仔細閱讀此文:http://blog.csdn.net/coolmeme/article/details/9997609 。這篇文章提到了網路伺服器並發串連數、吐吞量、寬頻概念,對於初學者應該很受用。
Goruntine
goruntine原理
我們知道Go從語言層面就支援了並發,而goruntine是go並發設計的核心。goruntine說到底是協程【Go Web 編程裡是線程,也是對的,因為協程類似於使用者態線程】。具體原理實現參考:
1. 以goroutine為例看協程的相關概念
2. goroutine與調度器
3. 廖雪峰:協程
4. 知乎:協程的好處是什嗎?
5. 知乎:golang的goroutine是如何?的?
這些參考文章建議讀者好好看看。
瞭解了協程、goruntine的實現機制,接下來學習如何啟動goruntine。
啟動goruntine
goroutine 通過關鍵字 go 就啟動了一個 goroutine。
go hello(a, b, c)//普通函數前加go
例子:
package mainimport ( "fmt" "runtime")func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched() //表示讓cpu將控制權給其他人 fmt.Println(s) }}func main() { runtime.GOMAXPROCS(1) go say("world") say("hello")}
輸出:
helloworldhelloworldhelloworldhelloworldhello
很簡單,在函數前加一個go關鍵詞就啟動了goruntine。
Channel
channel是什麼
channel是一種通訊通道,goruntine之間的資料通訊通過channel來實現。goruntine通過channel發送或者接收訊息。
channel的基本操作文法:
cl := make(chan int) //建立一個無緩衝的int型channel,可以根據需求建立bool、string等類型的channelc1 := make(chan int, 4) //建立有緩衝的int型channelcl <- x //發送x到channel clx := <- cl //從cl中接收資料,並賦值給x
無緩衝的例子:
package mainimport ( "fmt" "time")func sendChan(cl chan string) { fmt.Println("[send_start]") cl <- "hello world" // 向cl中加資料,如果沒有其他goroutine來取走這個資料,那麼掛起sendChan, 直到getChan函數把"hello world"這個資料拿走 fmt.Println("[send_end]")}func getChan(cl chan string) { fmt.Println("[get_start]") s := <-cl // 從cl取資料,如果cl中還沒放資料,那就掛起getChan線程,直到sendChan函數中放資料為止 fmt.Println("[get_end]" + s)}func main() { cl := make(chan string) go sendChan(cl) go getChan(cl) time.Sleep(time.Second)}
輸出:
[send_start][get_start][get_end]hello world[send_end]
上面的例子存在3個goruntine,注意main也在一個goruntine中。如果函數main中沒有 time.Sleep(time.Second),你會發現什麼輸出都不會有,為什麼呢?是因為另外兩個goruntine還沒來得及跑,主函數main就已經退出了。
所以需要讓main等一下,time.Sleep(time.Second)就是讓main停頓一秒再輸出。
無緩衝的channel的接收和發送都是阻塞的,也就是說:
- 資料流入無緩衝通道, 如果沒有其他goroutine來拿走這個資料,那麼當前線阻塞
- 從無緩衝通道取資料,必須要有資料流進來才可以,否則當前goroutine阻塞
有緩衝的例子:
package mainimport ( "fmt" "time")func sendChan(cl chan int, len int) { fmt.Println("sendChan_enter") for i := 0; i < len; i++ { fmt.Println("# ", i) cl <- i //cl的儲存第4個資料的時候,會阻塞當前goruntine,直到其它goruntine取走一個或多個資料 } fmt.Println("sendChan_end")}func getChan(cl chan int, len int) { fmt.Println("getChan_enter") for i := 0; i < len; i++ { data := <-cl fmt.Println("$ ", data)//當cl的資料為空白時,阻塞當前goruntine,直到新的資料寫入cl } fmt.Println("getChan_end")}func main() { cl := make(chan int, 3)// 寫入3個元素都不會阻塞當前goroutine, 儲存個數達到4的時候會阻塞 go sendChan(cl, 10) go getChan(cl, 5) time.Sleep(time.Second)}
輸出:
sendChan_enter# 0# 1# 2# 3getChan_enter$ 0$ 1$ 2$ 3# 4# 5# 6# 7# 8$ 4getChan_end
為什麼sendChan_end沒有輸出?
getChan取完5個資料後,getChan這個goruntine就會掛起,而sendChan線程因為資料填滿,無法將剩餘的資料寫入chanl而掛起,最後因main所在的goruntine逾時1秒結束而結束。故而看不到sendChan_end的輸出。
- 有緩衝的channel是可以無阻塞的寫入,當緩衝填滿時,再次寫入新的資料時,當前goruntine會發生阻塞,直到其它goruntine從channel中取走一些資料:
- 有緩衝的channel可以無阻塞的擷取資料,當資料取空時,再次取新的資料時,當前的goruntine會發生阻塞,直到其它goruntine往channel寫入新的資料
close
生產者【發送channel的goruntine】通過關鍵字 close 函數關閉 channel。關閉 channel 之後就無法再發送任何資料了, 在消費方【接收channel的goruntine】可以通過文法 v, ok := <-ch 測試 channel 是否被關閉。如果 ok 返回 false,那麼說明 channel 已經沒有任何資料並且已經被關閉。
不過一般用得少,網上關於它的描述也不多。
select
文法結構類似於switch。
select { case cl<-x: go語句 case <-cl: go語句 default: //可選, go語句}
- 每個case只能是channel的擷取或者寫入,不能是其它語句。
- 當每個case都無法執行,如果有default,執行default;如果沒有default,當前goruntine阻塞。
- 當多個case都可以執行的時候,隨機選出一個執行。
關於select的用法,強烈推薦閱讀:【GOLANG】Go語言學習-select用法