Go通關09:併發掌握,goroutine和channel宣告與使用!
什麼是程序、執行緒
程序就是一個應用程式的工作空間,比如你開啟的QQ,微信,工作空間包含了該程式執行所需的所有資源。而執行緒是程序中的執行單位,一個程序最少有一個執行緒。
程序與執行緒對比
- 程序是系統資源分配和排程的最小單位
- 執行緒是程式執行的最小單位
- 一個程序由一個或多個執行緒組成,執行緒是程序中程式碼的不同執行路線
- 程序之間相互獨立,程序中的執行緒共享程式的記憶體空間及資源
- 執行緒在時間效率和空間效率都比程序要高
協程
協程是一種使用者態的輕量級執行緒,執行緒是CPU來排程,而協程的排程完全是由使用者來控制的。
協程與執行緒對比
- 一個執行緒可以有多個協程
- 執行緒、程序都是同步機制,而協程是非同步
- 協程可以保留上一次呼叫時的狀態,當過程重入時,相當於進入了上一次的呼叫狀態
- 協程是需要執行緒來承載執行的,所以協程並不能取代執行緒,執行緒是被分割的CPU資源,協程是組織好的程式碼流程
併發、並行
- 併發和並行是相對於程序或者執行緒來說的。
- 併發是一個或多個CPU對多個程序/執行緒之間的多路複用,通俗講就是CPU輪流執行多個任務,而每個任務都執行一小段,從巨集觀來看就像在同時執行。
- 並行必須有多個CPU來提供支援,真正意義上的在同一時刻執行多個程序或執行緒。
Go語言協程
Go中沒有執行緒的概念,只有協程(goroutine),協程相比執行緒更加輕量,上下文切換更快。Goroutine由 Go 自己來排程,我們只管啟用。
goroutine 通過 go
go function()
package main
import (
"fmt"
"time"
)
func main (){
go fmt.Println("微客鳥窩")
fmt.Println("我是無塵啊")
time.Sleep(time.Second) //等待一秒,使goroutine 執行完畢
}
執行結果:
我是無塵啊
微客鳥窩
Channel
channel(通道) 是用來解決多個 goroutine 之間通訊問題的。
在 Go 語言中,提倡通過通訊來共享記憶體,而不是通過共享記憶體來通訊,其實就是提倡通過 channel 傳送接收訊息的方式進行資料傳遞,而不是通過修改同一個變數。所以在資料流動、傳遞的場景中要優先使用 channel,它是併發安全的,效能也不錯。
channel 宣告
ch := make(chan string)
- 使用 make 函式
- chan 是關鍵字,表示 channel 型別,chan 是一個集合型別
- string 表示 channel 裡存放資料的型別
chan 使用
chan 只有傳送和接收兩種操作:
- 傳送: <-chan //向chan內傳送資料
- 接收: chan-> //從chan中獲取資料
示例:
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
go func(){
fmt.Println("微客鳥窩")
ch <- "執行完畢"
}()
fmt.Println("我是無塵啊")
value := <-ch
fmt.Println("獲取的chan的值為:",value)
}
執行結果
我是無塵啊
微客鳥窩
獲取的chan的值為: 執行完畢
有了 chan 後,我們可以不使用 Sleep 來等 goroutine執行完了,因為接收操作(
value := <-ch
)會阻塞等待,直到它獲取到值為止。
無緩衝 channel
上面的操作就是一個無緩衝 channel,通道的容量是0,它不能儲存資料,只是起到了傳輸的作用,所以無緩衝 channel 的傳送和接收操作是同時進行的
有緩衝 channel
在宣告的時候,我們可以傳入第二個引數,即channel容量大小,這樣就是建立了一個有緩衝 channel。
//建立一個容量為3的 channel,其內部可以存放3個型別為int的元素
ch := make(chan int,3)
- 有緩衝 channel 內部有一個佇列
- 傳送操作是向佇列尾部追加元素,如果佇列滿了,則阻塞等待,直到接收操作從佇列中取走元素。
- 接收操作是從佇列頭部取走元素,如果佇列為空,則阻塞等待,直到傳送操作向佇列追加了元素。
- 可以通過內建函式
cap
來獲取 channel 的容量,通過內建函式 len 獲取 channel 中元素個數。
ch := make(chan int,3)
ch <- 1
ch <- 2
fmt.Println("容量為",cap(ch),"元素個數為:",len(ch))
//列印結果:容量為 3 元素個數為: 2
關閉 channel
使用內建函式 close :close(ch)
- channel 關閉了就不能再向其傳送資料了,否則會引起 panic 異常。
- 可以從關閉了的 channel 中接收資料,如果沒資料,則接收到的是元素型別的零值。
單向 channel
只能傳送或者只能接收的 channel 為單向 channel。
單向 channel 宣告
只需要在基礎宣告中增加操作符即可:
send := make(ch<- int) //只能傳送資料給channel
receive := make(<-ch int) //只能從channel中接收資料
示例:
package main
import (
"fmt"
)
//只能傳送通道
func send(s chan<- string){
s <- "微客鳥窩"
}
//只能接收通道
func receive(r <-chan string){
str := <-r
fmt.Println("str:",str)
}
func main() {
//建立一個雙向通道
ch := make(chan string)
go send(ch)
receive(ch)
}
//執行結果: str: 微客鳥窩
select+channel
select 可以實現多路複用,即同時監聽多個 channel。
- 發現哪個 channel 有資料產生,就執行相應的 case 分支
- 如果同時有多個 case 分支可以執行,則會隨機選擇一個
- 如果一個 case 分支都不可執行,則 select 會一直等待
示例:
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
for i := 0; i < 10; i++ {
select {
case x := <-ch:
fmt.Println(x)
case ch <- i:
fmt.Println("--", i)
}
}
}
執行結果:
-- 0
0
-- 2
2
-- 4
4
-- 6
6
-- 8
8
此處留作思考題,為何會這樣輸出呢?