1.什么是Channel?
channel
即Go
的通道潦嘶,是協(xié)程之間的通信機(jī)制榛丢。一個(gè)channel
是一條通信管道卡辰,它可以讓一個(gè)協(xié)程通過它給另一個(gè)協(xié)程發(fā)送數(shù)據(jù)。每個(gè)channel
都需要指定數(shù)據(jù)類型,即channel
可發(fā)送數(shù)據(jù)的類型族沃。Go語言主張通過數(shù)據(jù)傳遞來實(shí)現(xiàn)共享內(nèi)存误辑,而不是通過共享內(nèi)存來實(shí)現(xiàn)數(shù)據(jù)傳遞。
2. 創(chuàng)建Channel
2.1 語法
channel
是引用類型尤泽,需要使用make()
進(jìn)行創(chuàng)建欣簇。
// 聲明方式1
var cha1 chan 數(shù)據(jù)類型
cha1 = make(chan 數(shù)據(jù)類型)
// 聲明方式2
cha1 := make(chan 數(shù)據(jù)類型)
2.2 使用示例
package main
import (
"fmt"
)
type People struct {}
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
fmt.Printf("intChan類型: %T 值: %v \n",intChan,intChan)
// 創(chuàng)建一個(gè)空接口chan,可以存放任意類型數(shù)據(jù)
interfaceChan := make(chan interface{})
fmt.Printf("interfaceChan類型: %T 值: %v \n",interfaceChan,interfaceChan)
// 創(chuàng)建一個(gè)指針chan
peopleChan := make(chan *People)
fmt.Printf("peopleChan類型: %T 值: %v \n",peopleChan,peopleChan)
}
/** 輸出
intChan類型: chan int 值: 0xc000052060
interfaceChan類型: chan interface {} 值: 0xc0000520c0
peopleChan類型: chan *main.People 值: 0xc000052120
*/
3.發(fā)送數(shù)據(jù)
3.1 語法
通過channel
發(fā)送數(shù)據(jù)需要使用特殊的操作符<-
,需要注意的是: channel
發(fā)送的值的類型必須與channel
的元素類型一致坯约。
channel變量名 <- 值
3.2 錯(cuò)誤使用示例
package main
import (
"fmt"
"time"
)
type People struct {
}
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 寫入
intChan <- 5
fmt.Printf("intChan類型: %T 值: %v \n", intChan, intChan)
}
上面示例運(yùn)行會(huì)死鎖熊咽,報(bào)錯(cuò)內(nèi)容如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/Users/hui/Project/Go/src/go-basic/main.go:12 +0x59
Process finished with exit code 2
報(bào)錯(cuò)原因: 如果Goroutine在一個(gè)channel上發(fā)送數(shù)據(jù),其他的Goroutine應(yīng)該接收得到數(shù)據(jù)鬼店;如果沒有接收网棍,那么程序?qū)⒃谶\(yùn)行時(shí)出現(xiàn)死鎖。
3.3 正確使用示例
package main
import (
"fmt"
)
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 寫入數(shù)據(jù)(協(xié)程寫入)
go sendMsg(intChan)
// 接收數(shù)據(jù)(主線程讀取)
a := <- intChan
fmt.Printf("接收數(shù)據(jù): %v \n", a)
fmt.Println("運(yùn)行結(jié)束! ")
}
func sendMsg(intChan chan int ){
// 寫入
intChan <- 5
fmt.Println("寫入數(shù)據(jù): 5 ")
}
/** 輸出:
寫入數(shù)據(jù): 5
接收數(shù)據(jù): 5
運(yùn)行結(jié)束!
*/
4.普通接收
channel
接收同樣使用特殊的操作符<-
妇智。
4.1 阻塞接收語法
// 方式一: ch 指的是通道變量
data := <- ch
//方式二: data 表示接收到的數(shù)據(jù)滥玷。未接收到數(shù)據(jù)時(shí),data為channel類型的零值巍棱,ok(布爾類型)表示是否接收到數(shù)據(jù)
data,ok := <- ch
執(zhí)行該語句時(shí)channel將會(huì)阻塞惑畴,直到接收到數(shù)據(jù)并賦值給data變量。
4.2 忽略接收語法
<- ch
執(zhí)行該語句時(shí)
channel
將會(huì)阻塞航徙。其目的不在于接收channel
中數(shù)據(jù)如贷,而是為了阻塞Goroutine
。
如果Goroutine
正在等待從channel
接收數(shù)據(jù)到踏,而其他Goroutine
并沒有寫入數(shù)據(jù)時(shí)程序?qū)?huì)死鎖杠袱。
5. 循環(huán)接收
循環(huán)接收數(shù)據(jù),需要配合使用關(guān)閉channel
窝稿,借助普通for
循環(huán)和for ... range
語句循環(huán)接收多個(gè)元素楣富。遍歷channel
,遍歷的結(jié)果就是接收到的數(shù)據(jù)伴榔,數(shù)據(jù)類型就是channel
的數(shù)據(jù)類型纹蝴。普通for
循環(huán)接收channel
數(shù)據(jù)庄萎,需要有break
循環(huán)的條件;for … range
會(huì)自動(dòng)判斷出channel
已關(guān)閉塘安,而無須通過判斷來終止循環(huán)糠涛。
5.1 使用普通for接收
方式一: data := <- ch
package main
import "fmt"
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 寫入數(shù)據(jù)(協(xié)程寫入)
go func(cha chan int) {
// 寫入
for i := 1; i < 5; i++ {
intChan <- i
fmt.Printf("寫入數(shù)據(jù) -> %v \n", i)
}
// 關(guān)閉通道
close(intChan)
}(intChan)
// 方式一: data := <- ch
for {
// 接收數(shù)據(jù)
out := <-intChan
// 判斷通道是否關(guān)閉
//如果通道關(guān)閉,則out為通道類型的零值兼犯,這里是int型忍捡,所以是0
if out == 0 {
fmt.Println("通道已關(guān)閉")
break
}
fmt.Printf("接收數(shù)據(jù) ==> %v \n", out)
}
fmt.Println("程序運(yùn)行結(jié)束!")
}
/** 輸出:
寫入數(shù)據(jù) -> 1
接收數(shù)據(jù) ==> 1
接收數(shù)據(jù) ==> 2
寫入數(shù)據(jù) -> 2
寫入數(shù)據(jù) -> 3
接收數(shù)據(jù) ==> 3
接收數(shù)據(jù) ==> 4
寫入數(shù)據(jù) -> 4
通道已關(guān)閉
程序運(yùn)行結(jié)束!
*/
方式二: data,ok := <- ch
package main
import "fmt"
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 寫入數(shù)據(jù)(協(xié)程寫入)
go func(cha chan int) {
// 寫入
for i := 1; i < 5; i++ {
intChan <- i
fmt.Printf("寫入數(shù)據(jù) -> %v \n", i)
}
// 關(guān)閉通道
close(intChan)
}(intChan)
// 方式二: data,ok := <- ch
for {
// 接收數(shù)據(jù)
out,ok := <-intChan
// 判斷通道是否關(guān)閉,如果通道關(guān)閉,則ok為false
if !ok {
fmt.Println("通道已關(guān)閉")
break
}
fmt.Printf("接收數(shù)據(jù) ==> %v \n", out)
}
fmt.Println("程序運(yùn)行結(jié)束!")
}
/** 輸出:
寫入數(shù)據(jù) -> 1
接收數(shù)據(jù) ==> 1
接收數(shù)據(jù) ==> 2
寫入數(shù)據(jù) -> 2
寫入數(shù)據(jù) -> 3
接收數(shù)據(jù) ==> 3
接收數(shù)據(jù) ==> 4
寫入數(shù)據(jù) -> 4
通道已關(guān)閉
程序運(yùn)行結(jié)束!
*/
5.2 使用for...range接收
package main
import "fmt"
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 寫入數(shù)據(jù)(協(xié)程寫入)
go func(cha chan int) {
// 寫入
for i := 1; i < 5; i++ {
intChan <- i
fmt.Printf("寫入數(shù)據(jù) -> %v \n", i)
}
// 關(guān)閉通道
close(intChan)
}(intChan)
// 使用 for...range接收
for data := range intChan {
fmt.Printf("接收數(shù)據(jù) ==> %v \n", data)
}
fmt.Println("程序運(yùn)行結(jié)束!")
}
/** 輸出:
寫入數(shù)據(jù) -> 1
接收數(shù)據(jù) ==> 1
接收數(shù)據(jù) ==> 2
寫入數(shù)據(jù) -> 2
寫入數(shù)據(jù) -> 3
接收數(shù)據(jù) ==> 3
接收數(shù)據(jù) ==> 4
寫入數(shù)據(jù) -> 4
程序運(yùn)行結(jié)束!
*/
6. Channle的阻塞特性
6.1 特性如下
-
channel
默認(rèn)是阻塞的免都。 - 當(dāng)數(shù)據(jù)被發(fā)送到
channel
時(shí)會(huì)發(fā)生阻塞锉罐,直到有其他Goroutine
從該channel
中讀取數(shù)據(jù)。 - 當(dāng)從
channel
讀取數(shù)據(jù)時(shí)绕娘,讀取也會(huì)被阻塞脓规,直到其他Goroutine
將數(shù)據(jù)寫入該channel
。
6.2 特性使用
package main
import "fmt"
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 創(chuàng)建一個(gè)用于阻塞的chan
boolChan := make(chan bool)
// 創(chuàng)建一個(gè)寫入數(shù)據(jù)的協(xié)程
go func(cha chan int) {
// 寫入
intChan <- 50
fmt.Println("寫入數(shù)據(jù)50")
// 關(guān)閉通道
close(intChan)
}(intChan)
// 創(chuàng)建一個(gè)讀取數(shù)據(jù)的協(xié)程
go func(intChan chan int, boolChan chan bool) {
data,ok := <- intChan
if ok {
fmt.Printf("讀取到數(shù)據(jù) -> %v \n", data)
// 讀取到數(shù)據(jù)后险领,給boolChan寫入值
boolChan <- true
// 關(guān)閉用于的阻塞的chan
close(boolChan)
}
}(intChan,boolChan)
// 忽略接收侨舆,達(dá)到阻塞的效果。(如果不阻塞绢陌,則會(huì)直接輸出: 程序運(yùn)行結(jié)束!,不會(huì)等待協(xié)程執(zhí)行)
<- boolChan
fmt.Println("程序運(yùn)行結(jié)束!")
}
/** 輸出
寫入數(shù)據(jù)50
讀取到數(shù)據(jù) -> 50
程序運(yùn)行結(jié)束!
*/
阻塞channel等待匿名函數(shù)的Goroutine運(yùn)行結(jié)束挨下,防止主函數(shù)的Goroutine退出而導(dǎo)致匿名函數(shù)的Goroutine提前退出。
7.關(guān)閉Channel
發(fā)送方寫入完畢后需要主動(dòng)關(guān)閉channel
脐湾,用于通知接收方數(shù)據(jù)傳遞完畢臭笆。接收方通過data,ok := <- ch
判斷channel
是否關(guān)閉,如果ok=false
秤掌,則表示channel
已經(jīng)被關(guān)閉愁铺。
7.1 使用示例
package main
import "fmt"
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 創(chuàng)建一個(gè)寫入channel的協(xié)程
go func(intChan chan int) {
intChan <- 10
intChan <- 20
// 關(guān)閉通道
close(intChan)
}(intChan)
// 讀取數(shù)據(jù)
a := <- intChan
fmt.Printf("接收數(shù)據(jù): %v \n",a)
b := <- intChan
fmt.Printf("接收數(shù)據(jù): %v \n",b)
// 此時(shí)的Chan已經(jīng)關(guān)閉,而且里面的數(shù)據(jù)也都已經(jīng)取完
c := <- intChan
fmt.Printf("接收數(shù)據(jù): %v \n",c)
fmt.Println("程序運(yùn)行結(jié)束!")
}
/** 輸出
接收數(shù)據(jù): 10
接收數(shù)據(jù): 20
接收數(shù)據(jù): 0
程序運(yùn)行結(jié)束!
*/
又上面示例可以看出: 可以從關(guān)閉后的
channel
中繼續(xù)讀取數(shù)據(jù)闻鉴,取到的值為該類型的零值茵乱。比如整型是:0; 字符串是:""
7.2 向已關(guān)閉的chan寫入數(shù)據(jù),會(huì)崩潰
往關(guān)閉的channel中寫入數(shù)據(jù)會(huì)報(bào)錯(cuò):panic: send on closed channel。導(dǎo)致程序崩潰孟岛。
package main
import "fmt"
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 創(chuàng)建一個(gè)寫入channel的協(xié)程
go func(intChan chan int) {
intChan <- 10
// 關(guān)閉通道
close(intChan)
// 向已關(guān)閉的chan 繼續(xù)寫入數(shù)據(jù)瓶竭,會(huì)報(bào)錯(cuò);
intChan <- 20
}(intChan)
// 讀取數(shù)據(jù)
a := <- intChan
fmt.Printf("接收數(shù)據(jù): %v \n",a)
b := <- intChan
fmt.Printf("接收數(shù)據(jù): %v \n",b)
fmt.Println("程序運(yùn)行結(jié)束!")
}
/** 輸出:
接收數(shù)據(jù): 10
接收數(shù)據(jù): 0
panic: send on closed channel
goroutine 18 [running]:
main.main.func1(0xc000100060)
/Users/hui/Project/Go/src/go-basic/main.go:14 +0x5f
created by main.main
/Users/hui/Project/Go/src/go-basic/main.go:10 +0x6a
Process finished with exit code 2
*/
7.3 重復(fù)關(guān)閉chan,會(huì)崩潰
package main]
import "fmt"
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
// 創(chuàng)建一個(gè)寫入channel的協(xié)程
go func(intChan chan int) {
intChan <- 10
// 關(guān)閉通道
close(intChan)
}(intChan)
// 讀取數(shù)據(jù)
a := <- intChan
fmt.Printf("接收數(shù)據(jù): %v \n",a)
b := <- intChan
fmt.Printf("接收數(shù)據(jù): %v \n",b)
// 重復(fù)關(guān)閉(此處會(huì)報(bào)錯(cuò))
close(intChan)
fmt.Println("程序運(yùn)行結(jié)束!")
}
/** 輸出
接收數(shù)據(jù): 10
接收數(shù)據(jù): 0
panic: close of closed channel
goroutine 1 [running]:
main.main()
/Users/hui/Project/Go/src/go-basic/main.go:22 +0x1cc
Process finished with exit code 2
*/
8.緩沖Channel
默認(rèn)創(chuàng)建的都是非緩沖channel
渠羞,讀寫都是即時(shí)阻塞怨规。緩沖channel
自帶一塊緩沖區(qū)芥吟,可以暫時(shí)存儲(chǔ)數(shù)據(jù)兔仰,如果緩沖區(qū)滿了胜卤,就會(huì)發(fā)生阻塞浸间。緩沖通道在發(fā)送時(shí)無需等待接收方接收即可完成發(fā)送過程玩徊,并且不會(huì)發(fā)生阻塞僧叉,只有當(dāng)緩沖區(qū)滿時(shí)才會(huì)發(fā)生阻塞拌滋。同理,如果緩沖通道中有數(shù)據(jù)雌芽,接收時(shí)將不會(huì)發(fā)生阻塞授艰,直到通道中沒有數(shù)據(jù)可讀時(shí),通道將會(huì)再度阻塞世落。
8.1 語法
// 聲明 n:代表緩沖區(qū)大小
cha1 := make(chan T,n)
8.2 使用示例
package main
import (
"fmt"
"time"
)
func main() {
fmt.Printf("開始時(shí)間: %v \n",time.Now().Unix())
// 創(chuàng)建一個(gè)緩沖區(qū)為2的整數(shù)型chan
intChan2 := make(chan int,2)
// 不會(huì)發(fā)生阻塞淮腾,因?yàn)榫彌_區(qū)未滿
intChan2 <- 100
fmt.Printf("結(jié)束時(shí)間: %v \n",time.Now().Unix())
fmt.Printf("intChan2 類型: %T 緩沖大小: %v \n",intChan2,cap(intChan2))
fmt.Println("程序運(yùn)行結(jié)束!")
}
/**輸出:
開始時(shí)間: 1607496281
結(jié)束時(shí)間: 1607496281
intChan2 類型: chan int 緩沖大小: 2
程序運(yùn)行結(jié)束!
*/
9.單向Channel
channel
默認(rèn)都是雙向的,即可讀可寫屉佳。定向channel
也叫單向channel
谷朝,只讀或只寫。直接創(chuàng)建單向channel
沒有任何意義武花。通常的做法是創(chuàng)建雙向channel
圆凰,然后以單向channel
的方式進(jìn)行函數(shù)傳遞。
9.1 介紹
// 只讀
ch <- chan T
// 只寫
ch chan <- T
9.1 使用
package main
import (
"fmt"
"time"
)
func main() {
// 創(chuàng)建一個(gè)整數(shù)型chan
intChan := make(chan int)
go writeChan(intChan)
go readChan(intChan)
time.Sleep(50 * time.Millisecond)
fmt.Println("運(yùn)行結(jié)束")
}
// 定義只讀通道的函數(shù)
func readChan( ch <- chan int) {
for data := range ch {
fmt.Printf("讀出數(shù)據(jù): %v \n",data)
}
}
// 定義只寫通道的函數(shù)
func writeChan( ch chan <- int){
for i:= 1; i< 5 ; i++ {
ch <- i
fmt.Printf("寫入數(shù)據(jù): %v \n",i)
}
close(ch)
}
10.計(jì)時(shí)器與channel
計(jì)時(shí)器類型表示單個(gè)事件体箕。當(dāng)計(jì)時(shí)器過期時(shí)专钉,當(dāng)前時(shí)間將被發(fā)送到c
上(c是一個(gè)只讀channel <-chan time.Time,該channel中放入的是Timer結(jié)構(gòu)體
)累铅,除非計(jì)時(shí)器是After()
創(chuàng)建的跃须。計(jì)時(shí)器必須使用NewTimer()
或After()
創(chuàng)建。
10.1 NewTimer
NewTimer()
創(chuàng)建一個(gè)新的計(jì)時(shí)器娃兽,它會(huì)在至少持續(xù)時(shí)間d
之后將當(dāng)前時(shí)間發(fā)送到其channel
上菇民。
使用示例:
package main
import (
"fmt"
"time"
)
func main() {
// 創(chuàng)建一個(gè)計(jì)時(shí)器
timer := time.NewTimer(5 * time.Second)
fmt.Printf("開始時(shí)間 %v \n",time.Now())
// 此處會(huì)阻塞5秒
out := <- timer.C
fmt.Printf("變量out-> 類型: %T 值:%v \n",out,out)
fmt.Printf("開始時(shí)間 %v \n",time.Now())
}
/** 輸出:
開始時(shí)間 2020-12-10 10:53:22.979673 +0800 CST m=+0.000174275
變量out-> 類型: time.Time 值:2020-12-10 10:53:27.980079 +0800 CST m=+5.000489969
開始時(shí)間 2020-12-10 10:53:27.980264 +0800 CST m=+5.000674880
*/
10.2 After
After()
函數(shù)相當(dāng)于NewTimer(d). C
,如下源碼:
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
使用示例
package main
import (
"fmt"
"time"
)
func main() {
// 創(chuàng)建一個(gè)計(jì)時(shí)器,返回的是chan
ch := time.After(5 * time.Second)
fmt.Printf("開始時(shí)間 %v \n",time.Now())
// 此處會(huì)阻塞5秒
out := <- ch
fmt.Printf("變量out-> 類型: %T 值:%v \n",out,out)
fmt.Printf("開始時(shí)間 %v \n",time.Now())
}
/** 輸出
開始時(shí)間 2020-12-10 11:01:07.272154 +0800 CST m=+0.000153152
變量out-> 類型: time.Time 值:2020-12-10 11:01:12.273034 +0800 CST m=+5.000956630
開始時(shí)間 2020-12-10 11:01:12.273153 +0800 CST m=+5.001076196
*/