并行與并發
理論式的概念:
并行:多件事在同一時刻發生。
并發:多件事在同一時間間隔發生。
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。
例子:
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i :=; i <; i++ {
runtime.Gosched() //表示讓cpu将控制權給其他人
fmt.Println(s)
}
}
func main() {
runtime.GOMAXPROCS)
go say("world")
say("hello")
}
輸出:
hello
world
hello
world
hello
world
hello
world
hello
很簡單,在函數前加一個go關鍵詞就啟動了goruntine。
Channel
channel是什麼
channel是一種通信通道,goruntine之間的資料通信通過channel來實作。goruntine通過channel發送或者接收消息。
channel的基本操作文法:
cl := make(chan int) //建立一個無緩沖的int型channel,可以根據需求建立bool、string等類型的channel
c1 := make(chan int,) //建立有緩沖的int型channel
cl <- x //發送x到channel cl
x := <- cl //從cl中接收資料,并指派給x
無緩沖的例子:
package main
import (
"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 main
import (
"fmt"
"time"
)
func sendChan(cl chan int, len int) {
fmt.Println("sendChan_enter")
for i :=; 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 :=; i < len; i++ {
data := <-cl
fmt.Println("$ ", data)//當cl的資料為空時,阻塞目前goruntine,直到新的資料寫入cl
}
fmt.Println("getChan_end")
}
func main() {
cl := make(chan int,)// 寫入3個元素都不會阻塞目前goroutine, 存儲個數達到4的時候會阻塞
go sendChan(cl,)
go getChan(cl,)
time.Sleep(time.Second)
}
輸出:
sendChan_enter
# 0
# 1
# 2
# 3
getChan_enter
$
$
$
$
# 4
# 5
# 6
# 7
# 8
$
getChan_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用法