通道channel被認(rèn)為是goroutine通信的管道。類似于水管里的水可以從一端流向另一端古涧,數(shù)據(jù)可以從一端發(fā)送到另一端,通過通道接收。
當(dāng)多個goroutine間想實現(xiàn)共享數(shù)據(jù)時闪金,可以使用傳統(tǒng)的同步機(jī)制(sync包的方法),但是go語言強(qiáng)烈建議使用channel通道來實現(xiàn)goroutine之間的通信论颅。
“不要通過共享內(nèi)存來通信哎垦,而應(yīng)該通過通信來共享內(nèi)存”這是一句風(fēng)靡golang社區(qū)的經(jīng)典語言。
Go語言中恃疯,要傳遞某個數(shù)據(jù)給另一個goroutine(協(xié)程)漏设,可以把這個數(shù)據(jù)封裝成一個對象,然后把這個對象的指針傳入某個channel中今妄,另外一個goroutine從這個channel中讀出這個指針郑口,并處理其指向的內(nèi)存對象鸳碧。Go從語言層面保證同一個時間只有一個goroutine能夠訪問channel里面的數(shù)據(jù),為開發(fā)者提供了一種優(yōu)雅簡單的工具犬性,所以Go的做法就是使用channel來通信瞻离,通過通信來傳遞內(nèi)存數(shù)據(jù),使得內(nèi)存數(shù)據(jù)在不同的goroutine中傳遞乒裆,而不是使用共享內(nèi)存來通信套利。
一、什么是通道
1缸兔、通道的概念
通道是什么日裙,通道就是goroutine之間的通道。它可以讓goroutine之間相互通信惰蜜。
每個通道都有與其相關(guān)的類型昂拂。該類型是通道允許傳輸?shù)臄?shù)據(jù)類型。
通道的零值為nil抛猖。nil通道沒有任何用處格侯,因此通道必須使用類似于map和切片的方法來定義。
2财著、通道的聲明
聲明通道联四,和聲明變量是一樣的
// 聲明通道
var 通道名稱 chan 數(shù)據(jù)類型
// 初始化
通道名稱 = make(chan 數(shù)據(jù)類型)
示例代碼:
package main
import "fmt"
func main() {
var c chan int
if c == nil {
fmt.Println("c聲明但是沒有初始化,是nil")
c = make(chan int)
fmt.Println(c == nil)
fmt.Printf("%T", c)
}
}
運行結(jié)果
c聲明但是沒有初始化撑教,是nil
false
chan int
也可以用簡短定義的方式聲明通道
通道名稱 := make(chan 數(shù)據(jù)類型)
3朝墩、通道數(shù)據(jù)類型
channel是引用類型的數(shù)據(jù),在作為參數(shù)傳遞時伟姐, 傳遞的是內(nèi)存地址收苏。
package main
import "fmt"
func main() {
c := make(chan int)
fmt.Printf("main:%T, %p\n", c, c)
test(c)
}
func test(c chan int) {
fmt.Printf("test:%T, %p\n", c, c)
}
運行結(jié)果
main:chan int, 0xc00001a0c0
test:chan int, 0xc00001a0c0
二、channel的使用
1愤兵、發(fā)送和接收
發(fā)送和接受的語法
// 讀取數(shù)據(jù)
data := <-chan
// 寫入數(shù)據(jù)
chan <- data
2鹿霸、發(fā)送和接收默認(rèn)是阻塞的
一個通道接收和發(fā)送數(shù)據(jù)默認(rèn)是阻塞的。也就是說秆乳,當(dāng)一個數(shù)據(jù)被發(fā)送到通道時懦鼠,在發(fā)送語句中被阻塞,直到另一個goroutine從該通道讀取數(shù)據(jù)屹堰,才解除阻塞肛冶。相應(yīng)地,當(dāng)從通道中讀取數(shù)據(jù)時扯键,讀取被阻塞淑趾,直到一個goroutine向該通道中寫入數(shù)據(jù)。
這些特性可以幫助goroutines有效地進(jìn)行通信忧陪。
示例代碼:
package main
import "fmt"
func main() {
c := make(chan bool)
go func() {
for i := 0; i < 5; i++ {
fmt.Println("i = ", i)
}
c <- true
}()
data := <-c
fmt.Println(data)
fmt.Println("main over")
}
運行結(jié)果
i = 0
i = 1
i = 2
i = 3
i = 4
true
main over
3扣泊、死鎖
只向通道中讀取或?qū)懭霐?shù)據(jù),而不寫入或讀取數(shù)據(jù)嘶摊,就會造成阻塞延蟹,產(chǎn)生死鎖。
示例代碼:
package main
func main() {
c := make(chan bool)
// fatal error: all goroutines are asleep - deadlock!
c <- true
}
4叶堆、通道使用注意事項:
- 用于goroutine阱飘,傳遞消息的。
- 通道虱颗,每個都有相關(guān)聯(lián)的數(shù)據(jù)類型,nil chan沥匈,不能使用,類似于nil map忘渔,不能直接存儲鍵值對高帖。
- 使用通道傳遞數(shù)據(jù):
<-
chan <- data
,發(fā)送數(shù)據(jù)到通道畦粮,向通道中寫數(shù)據(jù)
data <- chan
散址,從通道中獲取數(shù)據(jù),從通道中讀數(shù)據(jù) - 阻塞:
發(fā)送數(shù)據(jù):chan <- data
宣赔,阻塞的预麸,直到另一條goroutine,讀取數(shù)據(jù)來解除阻塞
讀取數(shù)據(jù):data <- chan
儒将,也是阻塞的吏祸。直到另一條goroutine,寫出數(shù)據(jù)解除阻塞钩蚊。 - 本身channel就是同步的贡翘,意味著同一時間,只能有一條goroutine來操作两疚。
5床估、關(guān)閉通道
發(fā)送者可以通過關(guān)閉通道,來通知接收方不會有更多的數(shù)據(jù)被發(fā)送到channel上诱渤。
close(chan)
接收者可以在接收來自通道的數(shù)據(jù)時使用額外的變量來檢查通道是否已經(jīng)關(guān)閉丐巫。
// ok 為true,表示成功的從通道中讀取數(shù)據(jù)
// ok 為false勺美,表示從關(guān)閉的通道中讀取數(shù)據(jù)递胧,將獲得通道數(shù)據(jù)類型的零值。
v, ok := <- chan
示例代碼:
package main
import "fmt"
func main() {
controlChan := make(chan bool)
c := make(chan int)
go writeData(c, controlChan)
go readData(c, controlChan)
_ = <-controlChan
fmt.Println("main over")
}
func writeData(c chan int, controlChan chan bool) {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
controlChan <- true
}
func readData(c chan int, controlChan chan bool) {
for {
v, ok := <-c
if !ok {
fmt.Println("數(shù)據(jù)讀取完畢")
break
}
fmt.Println("讀取數(shù)據(jù):", v)
}
controlChan <- true
}
運行結(jié)果
讀取數(shù)據(jù): 0
讀取數(shù)據(jù): 1
讀取數(shù)據(jù): 2
讀取數(shù)據(jù): 3
讀取數(shù)據(jù): 4
數(shù)據(jù)讀取完畢
main over
我們可以循環(huán)從通道上獲取數(shù)據(jù)赡茸,直到通道關(guān)閉缎脾。for循環(huán)的for range形式可用于從通道接收值,直到它關(guān)閉為止占卧。
示例代碼:
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go writeData(c)
// for循環(huán)的for range形式可用于從通道接收值遗菠,直到它關(guān)閉為止联喘。
for v := range c {
fmt.Println("讀取數(shù)據(jù):", v)
}
fmt.Println("main..over.....")
}
func writeData(c chan int) {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
}
三、緩沖通道
非緩沖通道
c := make(chan T)
緩沖通道
c := make(chan T辙纬,cap)
示例代碼:
package main
import (
"fmt"
)
func main() {
c := make(chan int, 3)
go writeData(c)
// for循環(huán)的for range形式可用于從通道接收值豁遭,直到它關(guān)閉為止。
for v := range c {
fmt.Println("\t讀取數(shù)據(jù):", v)
}
fmt.Println("main..over.....")
}
func writeData(c chan int) {
for i := 1; i <= 6; i++ {
c <- i
fmt.Println("寫入數(shù)據(jù): ", i)
}
close(c)
}
運行結(jié)果:
寫入數(shù)據(jù): 1
寫入數(shù)據(jù): 2
寫入數(shù)據(jù): 3
寫入數(shù)據(jù): 4
讀取數(shù)據(jù): 1
讀取數(shù)據(jù): 2
讀取數(shù)據(jù): 3
讀取數(shù)據(jù): 4
讀取數(shù)據(jù): 5
寫入數(shù)據(jù): 5
寫入數(shù)據(jù): 6
讀取數(shù)據(jù): 6
main..over.....
四贺拣、定向通道
// 雙向通道
c1 := make(chan T)
// 只寫通道
c2 := make(chan<- T)
// 只讀通道
c3 := make(<-chan T)
定義一個單向(定向)通道是沒有意義的蓖谢,單向通道往往用作函數(shù)的參數(shù)。
示例代碼:
package main
import (
"fmt"
)
func main() {
c := make(chan int, 3)
done := make(chan bool)
go writeData(c, done)
go readData(c, done)
<-done
fmt.Println("main..over.....")
}
func writeData(c chan<- int, done chan bool) {
for i := 1; i <= 6; i++ {
c <- i
fmt.Println("寫入數(shù)據(jù): ", i)
}
close(c)
done <- true
}
func readData(c <-chan int, done chan bool) {
for v := range c {
fmt.Println("\t讀取數(shù)據(jù):", v)
}
done <- true
}