golang無緩衝通道

chan類似隊列版管道,無緩衝chan看起來好像是全局變量,通過它可讓多個goroutine間通信。 這其實隱含一個事實,chan阻塞會引發goroutine上下文切換,而切換到哪一個可執行goroutine由go調度器決定(與阻塞chan相關)。go當前能夠使用的goroutine,必須在其待命隊列中,否則會產生死鎖。

上下文切換

多進程多線程都具備上下文切換,即保存恢復現場的能力。goroutine的上下文切換實現,是在用戶態基礎上進行,只不過它涉及到的資源比線程更少,如產生一個線程系統調用分配內存通常在1到8M,而goroutine只有4k,此外在使用寄存器,段位上,goroutine也只需3個左右,而線程則通常在10個左右。

無緩衝阻塞

go調度器對goroutine的使用配合chan,具有有序性。main函數是特殊的入口goroutine,若有阻塞代碼,運行時runtime會尋找已入隊列的goroutine並在適當的時機調用它。chan並不是全局變量,確切來說它的讀/寫阻塞會觸發當前goroutine執行權轉移,它只是個通信器。好似打電話,必須先知道對方號碼並有連線,才能正常工作,若順序不對,表現在golang中便是死鎖

Blocking

package main

import (
    "fmt"
)

func f1(in chan int) {
    fmt.Println(<-in)
}

func main() {
    out := make(chan int)
    out <- 2
    go f1(out)
}

上述代碼會產生死鎖,main入口goroutine,通道out產生了發送阻塞,此時runtime會嘗試調度與out通道讀相關的goroutine執行,但可惜的是,在 out <- 2之前,並沒有向go執行器隊列加入與out讀相關的goroutine。換句話而言,f1壓根就沒入隊,沒有執行機會。

unblocking

 package main

 import "fmt"

 func main() {
     out := make(chan int)
     go f1(out)
     // 此處順序大有講究,在使用發送通道之前必需想好數據接收的退路,f1即是
     out <- 2
 }

 func f1(in chan int) {
     fmt.Println(<-in)
 }

chan vs 全局變量

上文提到chan類似管道,管道顧名思義一端進一端出,很形象表明了一個連接器。go中的chan連接goroutine,遊離於衆多goroutine之間,功用性與全局變量有得一拼。但chan絕對不是全局變量,一個全局變量,可以在同一函數體內重複讀寫,但對無緩衝chan而言是不可以,原因在同一goroutine內對同一chan讀寫時,存在讀或寫阻塞面臨切換上下文,另一個對應的永遠沒執行機會,如下

  • 無緩衝通道死鎖
 package main
 import "fmt"

 func main() {
     ch := make(chan int)
     ch <- 5
     fmt.Println(<-ch)
 }
  • 有緩衝通道正常
 package main

 import "fmt"

 func main() {
     ch := make(chan int, 1)
     ch <- 5
     fmt.Println(<-ch)
 }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章