golang channel
<p> Go語言內建了書寫併發程式的工具。將go宣告放到一個需呼叫的函式之前,在相同地址空間呼叫執行這個函式,這樣該函式執行時便會作為一個獨立的併發執行緒。這種執行緒在Go語言中稱作goroutine。在這裡我要提一下,併發並不總是意味著並行。Goroutines是指在硬體允許情況下建立能夠並行執行程式的架構。 </p>
讓我們從一個例子開始:
func main() { // Start a goroutine and execute println concurrently go println("goroutine message") println("main function message") }
這段程式將輸出main function messageand 或者goroutine message。我說“ 或者”是因為催生的goroutine有一些特點。當你執行一個goroutine時,呼叫的程式碼(在我們的例子裡它是main函式)不等待goroutine完成,而是繼續往下執行。在呼叫完println後main函式結束了它的執行,在Go語言裡這意味著這個程式及所有催生的goroutines停止執行。但是,在這個發生之前,goroutine可能已經完成了其程式碼的執行並輸出了goroutine message字元。
你明白這些後必須有方法來避免這種情況。這就是Go語言中channels
Channels 基礎知識
Channels用來同步併發執行的函式並提供它們某種傳值交流的機制。Channels的一些特性:通過channel傳遞的元素型別、容器(或緩衝區)和傳遞的方向由“<-”操作符指定。你可以使用內建函式 make分配一個channel:
Channels是一個第一類值(一個物件在執行期間被建立,可以當做一個引數被傳遞,從子函式返回或者可以被賦給一個變數。)可以像其他值那樣在任何地方使用:作為一個結構元素,函式引數、函式返回值甚至另一個channel的型別:i := make(chan int) // by default the capacity is 0 s := make(chan string, 3) // non-zero capacity r := make(<-chan bool) // can only read from w := make(chan<- []os.FileInfo) // can only write to
// a channel which:
// - you can only write to
// - holds another channel as its value
c := make(chan<- chan bool)
// function accepts a channel as a parameter
func readFromChannel(input <-chan string) {}
// function returns a channel
func getChannel() chan bool {
b := make(chan bool)
return b
}
在讀、寫channel的時候要格外注意 <- 操作符。它的位置關乎到channel變數的讀寫操作。下面的例子標明瞭它的使用方法,但我還是要提醒你,這段程式碼
並不會被完整地執行,原因我們後面再講:
func main() {
c := make(chan int)
c <- 42 // 寫入channel
val := <-c // 從channel中讀取
println(val)
}
現在我們知道了什麼是channel,如何建立channel並且學了一些基礎操作。現在讓我們回到第一個示例,看看channel到底是如何幫助我們的。
func main() {
// 建立一個channel用以同步goroutine
done := make(chan bool)
// 在goroutine中執行輸出操作
go func() {
println("goroutine message")
// 告訴main函式執行完畢.
// 這個channel在goroutine中是可見的
// 因為它是在相同的地址空間執行的.
done <- true
}()
println("main function message")
<-done // 等待goroutine結束
}
這個程式將順溜地列印2條資訊。為什麼呢?因為channel沒有緩衝(我們沒有指定其容量)。所有基於未緩衝的channel的的操作會將操作鎖死直到輸出和接收全部準備就緒。這就是為什麼未緩衝channel也被稱作同步(synchronous)。在我們的例子中,主函式中的操作符<-將會把程式鎖死直到goroutine在channel中寫入資料。因此程式只有在讀取操作成功結束後才會終止。
為了避免存在一個channel的緩衝區所有讀取操作都在沒有鎖定的情況下順利完成(如果緩衝區是空的)並且寫入操作也順利結束(緩衝區不滿),這樣的channel被稱作非同步的channel。下面是一個用來描述這兩者區別的例子:
func main() {
message := make(chan string) // 無緩衝
count := 3
go func() {
for i := 1; i <= count; i++ {
fmt.Println("send message")
message <- fmt.Sprintf("message %d", i)
}
}()
time.Sleep(time.Second * 3)
for i := 1; i <= count; i++ {
fmt.Println(<-message)
}
}
在這個例子中,輸出資訊是一個同步的channel,程式輸出結果為:
send message
// 等待3秒
message 1
send message
send message
message 2
message 3
正如你所看到的,在第一次goroutine中寫入channel之後,其它在同一個channel中的寫入操作都被鎖住了,直到第一次讀取操作執行完畢(大約3秒)。
現在我們提供一個緩衝區給輸出資訊的channel,例如:定義初始化行將被改為:message := make(chan string, 2)。這次程式輸出將變為:
send message
send message
send message
// 等待3秒
message 1
message 2
message 3
這裡我們看到所有的寫操作的執行都不會等待第一次對緩衝區的讀取操作結束,channel允許儲存所有的三條資訊。通過修改channel容器,我們通過可以控制處理資訊的總數達到限制系統輸出的目的。
死鎖
現在讓我們回到前面那個沒有成功執行的讀/寫操作示例:
func main() {
c := make(chan int)
c <- 42 // 寫入channel
val := <-c // 讀取channel
println(val)
}
一旦執行此程式,你將得到以下錯誤:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/fullpathtofile/channelsio.go:5 +0x54
exit status 2
這個錯誤就是我們所知的死鎖. 在這種情況下,兩個goroutine互相等待對方釋放資源,造成雙方都無法繼續執行。GO語言可以在執行時檢測這種死鎖並報錯。這個錯誤是因為鎖的自身特性產生的。
程式碼在次以單執行緒的方式執行,逐行執行。向channel寫入的操作(c <- 42)會鎖住整個程式的執行程序,因為在同步channel中的寫操作只有在讀取器準備就緒後才能成功執行。然而在這裡,我們在寫操作的下一行才建立了讀取器。
為了使程式順利執行,我們需要做如下改動:
func main() {
c := make(chan int)
// 使寫操作在另一個goroutine中執行。
go func() {
c <- 42
}()
val := <-c
println(val)
}
範圍化的channels 和channel的關閉
在前面的一個例子中,我們向channel傳送了多條資訊並讀取它們,讀取器部分的程式碼如下:
for i := 1; i <= count; i++ {
fmt.Println(<-message)
}
為了在執行讀取操作的同時避免產生死鎖,我們需要知道傳送訊息的確切數目,因為我們不能讀取比寫入條數還多的資料。但是這樣很不方便,下面我們就提供了一個更為人性化的方法。
在Go語言中,存在一種稱為範圍表示式的程式碼,它允許程式反覆宣告陣列、字串、切片、圖和channel,重複宣告會一直持續到channel的關閉。請看下面的例子(雖然現在還不能執行):
func main() {
message := make(chan string)
count := 3
go func() {
for i := 1; i <= count; i++ {
message <- fmt.Sprintf("message %d", i)
}
}()
for msg := range message {
fmt.Println(msg)
}
}
很不幸的是,這段程式碼現在還不能執行。正如我們之前提到的,範圍(range)只有等到channel關閉後才會執行。因此我們需要使用
close 函式關閉channel,程式就會變成下面這個樣子:
go func() {
for i := 1; i <= count; i++ {
message <- fmt.Sprintf("message %d", i)
}
close(message)
}()
關閉channel還有另外一個好處——被關閉的channel內的讀取操作將不會引發鎖,而是始終長生預設的對應channel型別的值:
done := make(chan bool)
close(done)
//不會產生鎖,列印兩次false
//因為false是bool型別的預設值
println(<-done)
println(<-done)
這個特性可以被用於控制goroutine的同步,讓我們再回顧一下之前同步的例子:
func main() {
done := make(chan bool)
go func() {
println("goroutine message")
// 我們只關心被是否存在傳送這個事實,而不是值的內容。
done <- true
}()
println("main function message")
<-done
}
在這裡,done channel僅僅被用於同步程式執行,而不是傳送資料。再舉一個類似的例子:
func main() {
// 與資料內容無關
done := make(chan struct{})
go func() {
println("goroutine message")
// 傳送訊號"I'm done"
close(done)
}()
println("main function message")
<-done
}
我們關閉了goroutine中的channel,讀取操作不會產生鎖,因此主函式可以繼續執行下去。
<h3 id="content_h3_9_5"> 多channel模式和channel的選擇 </h3>
在真正的專案開發中,你可能需要多個goroutine和channel。當各部分的獨立性越強,他們之間也就越需要高效的同步措施。讓我們看個略微複雜的例子:
func getMessagesChannel(msg string, delay time.Duration) <-chan string {
c := make(chan string)
go func() {
for i := 1; i <= 3; i++ {
c <- fmt.Sprintf("%s %d", msg, i)
// 在傳送資訊前等待
time.Sleep(time.Millisecond * delay)
}
}()
return c
}
func main() {
c1 := getMessagesChannel("first", 300)
c2 := getMessagesChannel("second", 150)
c3 := getMessagesChannel("third", 10)
for i := 1; i <= 3; i++ {
println(<-c1)
println(<-c2)
println(<-c3)
}
}
這裡我們建立了一個方法,用來建立channel並定義了一個goroutine使之在一此呼叫中向channel傳送三條資訊。我們看到,c3理應是最後一次channel呼叫,所以它的輸出資訊應該在其它資訊之前。但是我們得到的卻是如下輸出:
first 1
second 1
third 1
first 2
second 2
third 2
first 3
second 3
third 3
<p> 顯然我們成功輸出了所有的資訊,這是因為第一個channel中的讀取操作在每個迴圈宣告中被鎖住300毫秒,其它操作必須隨之進入等待狀態。而我們期望的卻是從所有channel中儘快讀取資訊。 </p>
我們可以使用select 在多個channel之間進行選擇。這種選擇類似於普通的switch,但是所有的情況在這裡都是數值傳遞操作(讀/寫)。即使運算元增加,程式也不會在更多的鎖下執行。因此,如果想要達到我們之前的目的,我們可以這麼改寫程式:
for i := 1; i <= 9; i++ {
select {
case msg := <-c1:
println(msg)
case msg := <-c2:
println(msg)
case msg := <-c3:
println(msg)
}
}
注意迴圈中的9這個數:每個channel存在三個寫操作,這就是為什麼這裡需要9次迴圈的原因。在一般的守護程序中,我們可以使用無限迴圈執行選擇操作,但如果我在這裡那麼做了,那我們將得到一個死鎖:
first 1
second 1
third 1 // 這個channel將不會等待其他channel
third 2
third 3
second 2
first 2
second 3
first 3
總結.
channel是Go語言中頗為有趣的一個機制。但是在高效地使用它們之前你必須搞清楚他們是如何工作的。我試圖在本文中對channel做出最基礎的解釋,如果你想要更深入地學習這個機制,我建議你閱讀以下文章:
相關推薦
golang channel 的使用
參考 mark log port 準備 lan mar case .com 本文對channel使用中的幾個疑惑,以例子的形式加以說明。 普通channel 缺省情況下,發送和接收會一直阻塞著,直到另一方準備好. 例如: package main import (
golang channel tips
golang eal OS quest Golan real ann 發送 writing 1. 讀nil的channel是永遠阻塞的。關閉nil的channel會造成panic。 2. closed channel的行為: 向close的channel發消息會p
golang channel幾點總結
golang提倡使用通訊來共享資料,而不是通過共享資料來通訊。channel就是golang這種方式的體現。 Channel 在golang中有兩種channel:帶快取的和不帶快取。 帶快取的channel,也就是可以非同步收發的。 不帶快取的channel,
golang channel基本操作
channel可以實現執行緒的阻塞。 //建立無緩衝區channel,只能存放一個值。 var ch = make(chan int) //建立有緩衝區channel,可以存放多個值,值到達上限才會阻塞。 var ch1 = make(chan int,3) //賦值 ch<-555 //取值
golang channel 使用總結
原文地址 不同於傳統的多執行緒併發模型使用共享記憶體來實現執行緒間通訊的方式,golang 的哲學是通過 channel 進行協程(goroutine)之間的通訊來實現資料共享: Do not communicate by sharing memory; in
Golang-Channel原理解析
本文主要分析golang實現併發基礎元件channel的實現原理; 主要內容分為幾個部分 Section1:channel使用例項分析 Section2:原始碼分析 Golang-Channel原理解析 Section1 channel使用例項
golang channel
<p> Go語言內建了書寫併發程式的工具。將go宣告放到一個需呼叫的函式之前,在相同地址空間呼叫執行這個函式,這樣該函式執行時便會作為一個獨立的併發執行緒。這種執行緒在Go語言中稱作goroutine。在這裡我要提一下,併發並不總是意味
Golang channel實現
some pri 鏈表 oop ima 註釋 emc objects points Golang channel 初探 Goroutine和channel是Golang實現高並發的基礎。深入理解其背後的實現,寫起代碼來才不慌-_- 首先我們定義如下代碼,來看看Golang底
golang channel 有緩衝 與 無緩衝 的重要區別
golang channel 有緩衝 與 無緩衝 是有重要區別的 我之前天真的認為 有緩衝與無緩衝的區別 只是 無緩衝的 是 預設
golang 之 channel
microsoft 沒有 而已 創建 無緩沖 eight 不能 否則 false channel的機制是先進先出 無緩沖的channel: 如果你給channel賦值了,那麽必須要讀取它的值,不然就會造成阻塞。 chreadandwrite :=make
golang語言並發與並行——goroutine和channel的詳細理解
goroutin goroutine tin log http gpo ava post art http://blog.csdn.net/skh2015java/article/details/60330785 http://blog.csdn.net/skh2015j
golang 無限制同步隊列(unlimited buffer channel)
直接 支持 turn mov 恢復 done cnblogs int mark 問題 如何支持一個無容量限制的channel 取出元素會阻塞到元素存在並且返回 放入元素永遠不會阻塞,都會立即返回 方法一:用兩個chan加一個list模擬 在單獨的goroutine處理入
go語言之行--golang核武器goroutine調度原理、channel詳解
-s 丟失 一半 內核調度 保留 dea 等等 ado 線程 一、goroutine簡介 goroutine是go語言中最為NB的設計,也是其魅力所在,goroutine的本質是協程,是實現並行計算的核心。goroutine使用方式非常的簡單,只需使用go關鍵字即可啟動一
Golang中的channel代碼示例----無緩沖、有緩沖、range、close
數量 nbu -- 協程 channel 運行 package break dead // code_043_channel_unbuffered project main.go package main import ( "fmt" "time" )
golang並發編程之channel
val 地址空間 println 共享 pac shel pack 讀數 都是 一、概念channel是golang語言級別提供的協程(goroutine)之間的通信方式。goroutine運行在相同的地址空間,因此訪問共享內存必須做好同步。那麽goroutine之間如何進
golang檢視channel緩衝區的長度
golang提供內建函式cap用於檢視channel緩衝區長度。 cap的定義如下: func cap(v Type) int The cap built-in function returns the capacity of v, according to its type: - Array: th
18-golang通過channel實現斐波那契數列
寫斐波那契數列其實很簡單 但是我們用channel來寫 自我鍛鍊一下 也熟悉一下channel的用法 func main() { channel := make(chan int)
17-golang中單向channel的應用
將channel傳入方法 但是可以轉換為單向的channel channelSend chan<- int channelReceive <-chan int func main() { channel :
16-golang的無快取channel和有快取channel
我們先來看看無快取channel func main() { var channel = make(chan int, 0) go func() { for i := 0; i <= 2; i++ {
15-golang併發中channel的使用
我們先寫一段程式碼 func main() { go person1() go person2() for { } } func Printer(str string) { for _, ch := range str