1.什么是Channel?
channel
即Go
的通道,是协程之间的通信机制。一个channel
是一条通信管道,它可以让一个协程通过它给另一个协程发送数据。每个channel
都需要指定数据类型,即channel
可发送数据的类型。Go语言主张通过数据传递来实现共享内存,而不是通过共享内存来实现数据传递。
2. 创建Channel
2.1 语法
channel
是引用类型,需要使用make()
进行创建。
var cha1 chan 数据类型 cha1 = make(chan 数据类型)
cha1 := make(chan 数据类型)
|
2.2 使用示例
package main import ( "fmt" ) type People struct {} func main() { intChan := make(chan int) fmt.Printf("intChan类型: %T 值: %v \n",intChan,intChan) interfaceChan := make(chan interface{}) fmt.Printf("interfaceChan类型: %T 值: %v \n",interfaceChan,interfaceChan) peopleChan := make(chan *People) fmt.Printf("peopleChan类型: %T 值: %v \n",peopleChan,peopleChan) }
|
3.发送数据
3.1 语法
通过channel
发送数据需要使用特殊的操作符<-
,需要注意的是: channel
发送的值的类型必须与channel
的元素类型一致。
3.2 错误使用示例
package main import ( "fmt" "time" ) type People struct { } func main() { intChan := make(chan int) intChan <- 5 fmt.Printf("intChan类型: %T 值: %v \n", intChan, intChan) }
|
上面示例运行会死锁,报错内容如下:
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
|
报错原因: 如果Goroutine在一个channel上发送数据,其他的Goroutine应该接收得到数据;如果没有接收,那么程序将在运行时出现死锁。
3.3 正确使用示例
package main import ( "fmt" ) func main() { intChan := make(chan int) go sendMsg(intChan) a := <- intChan fmt.Printf("接收数据: %v \n", a) fmt.Println("运行结束! ") } func sendMsg(intChan chan int ){ intChan <- 5 fmt.Println("写入数据: 5 ") }
|
4.普通接收
channel
接收同样使用特殊的操作符<-
。
4.1 阻塞接收语法
data := <- ch
data,ok := <- ch
|
执行该语句时channel将会阻塞,直到接收到数据并赋值给data变量。
4.2 忽略接收语法
执行该语句时channel
将会阻塞。其目的不在于接收channel
中数据,而是为了阻塞Goroutine
。
如果Goroutine
正在等待从channel
接收数据,而其他Goroutine
并没有写入数据时程序将会死锁。
5. 循环接收
循环接收数据,需要配合使用关闭channel
,借助普通for
循环和for ... range
语句循环接收多个元素。遍历channel
,遍历的结果就是接收到的数据,数据类型就是channel
的数据类型。普通for
循环接收channel
数据,需要有break
循环的条件;for … range
会自动判断出channel
已关闭,而无须通过判断来终止循环。
5.1 使用普通for接收
方式一: data := <- ch
package main import "fmt" func main() { intChan := make(chan int) go func(cha chan int) { for i := 1; i < 5; i++ { intChan <- i fmt.Printf("写入数据 -> %v \n", i) } close(intChan) }(intChan)
for { out := <-intChan if out == 0 { fmt.Println("通道已关闭") break } fmt.Printf("接收数据 ==> %v \n", out) } fmt.Println("程序运行结束!") }
|
方式二: data,ok := <- ch
package main import "fmt" func main() { intChan := make(chan int) go func(cha chan int) { for i := 1; i < 5; i++ { intChan <- i fmt.Printf("写入数据 -> %v \n", i) } close(intChan) }(intChan)
for { out,ok := <-intChan if !ok { fmt.Println("通道已关闭") break } fmt.Printf("接收数据 ==> %v \n", out) } fmt.Println("程序运行结束!") }
|
5.2 使用for…range接收
package main import "fmt" func main() { intChan := make(chan int) go func(cha chan int) { for i := 1; i < 5; i++ { intChan <- i fmt.Printf("写入数据 -> %v \n", i) } close(intChan) }(intChan) for data := range intChan { fmt.Printf("接收数据 ==> %v \n", data) } fmt.Println("程序运行结束!") }
|
6. Channle的阻塞特性
6.1 特性如下
channel
默认是阻塞的。
- 当数据被发送到
channel
时会发生阻塞,直到有其他Goroutine
从该channel
中读取数据。
- 当从
channel
读取数据时,读取也会被阻塞,直到其他Goroutine
将数据写入该channel
。
6.2 特性使用
package main import "fmt" func main() { intChan := make(chan int) boolChan := make(chan bool)
go func(cha chan int) { intChan <- 50 fmt.Println("写入数据50") close(intChan) }(intChan)
go func(intChan chan int, boolChan chan bool) { data,ok := <- intChan if ok { fmt.Printf("读取到数据 -> %v \n", data) boolChan <- true close(boolChan) } }(intChan,boolChan) <- boolChan fmt.Println("程序运行结束!") }
|
阻塞channel等待匿名函数的Goroutine运行结束,防止主函数的Goroutine退出而导致匿名函数的Goroutine提前退出。
7.关闭Channel
发送方写入完毕后需要主动关闭channel
,用于通知接收方数据传递完毕。接收方通过data,ok := <- ch
判断channel
是否关闭,如果ok=false
,则表示channel
已经被关闭。
7.1 使用示例
package main import "fmt" func main() { intChan := make(chan int) go func(intChan chan int) { intChan <- 10 intChan <- 20 close(intChan) }(intChan)
a := <- intChan fmt.Printf("接收数据: %v \n",a) b := <- intChan fmt.Printf("接收数据: %v \n",b)
c := <- intChan fmt.Printf("接收数据: %v \n",c) fmt.Println("程序运行结束!") }
|
又上面示例可以看出: 可以从关闭后的channel
中继续读取数据,取到的值为该类型的零值。比如整型是:0; 字符串是:””
7.2 向已关闭的chan写入数据,会崩溃
往关闭的channel中写入数据会报错:panic: send on closed channel。导致程序崩溃。
package main import "fmt" func main() { intChan := make(chan int) go func(intChan chan int) { intChan <- 10 close(intChan) intChan <- 20 }(intChan) a := <- intChan fmt.Printf("接收数据: %v \n",a) b := <- intChan fmt.Printf("接收数据: %v \n",b) fmt.Println("程序运行结束!") }
|
7.3 重复关闭chan,会崩溃
package main] import "fmt" func main() { intChan := make(chan int)
go func(intChan chan int) { intChan <- 10 close(intChan) }(intChan)
a := <- intChan fmt.Printf("接收数据: %v \n",a) b := <- intChan fmt.Printf("接收数据: %v \n",b) close(intChan) fmt.Println("程序运行结束!") }
|
8.缓冲Channel
默认创建的都是非缓冲channel
,读写都是即时阻塞。缓冲channel
自带一块缓冲区,可以暂时存储数据,如果缓冲区满了,就会发生阻塞。缓冲通道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞,只有当缓冲区满时才会发生阻塞。同理,如果缓冲通道中有数据,接收时将不会发生阻塞,直到通道中没有数据可读时,通道将会再度阻塞。
8.1 语法
8.2 使用示例
package main import ( "fmt" "time" ) func main() { fmt.Printf("开始时间: %v \n",time.Now().Unix()) intChan2 := make(chan int,2) intChan2 <- 100 fmt.Printf("结束时间: %v \n",time.Now().Unix()) fmt.Printf("intChan2 类型: %T 缓冲大小: %v \n",intChan2,cap(intChan2)) fmt.Println("程序运行结束!") }
|
9.单向Channel
channel
默认都是双向的,即可读可写。定向channel
也叫单向channel
,只读或只写。直接创建单向channel
没有任何意义。通常的做法是创建双向channel
,然后以单向channel
的方式进行函数传递。
9.1 介绍
9.1 使用
package main import ( "fmt" "time" ) func main() { intChan := make(chan int) go writeChan(intChan) go readChan(intChan) time.Sleep(50 * time.Millisecond) fmt.Println("运行结束")
}
func readChan( ch <- chan int) { for data := range ch { fmt.Printf("读出数据: %v \n",data) } }
func writeChan( ch chan <- int){ for i:= 1; i< 5 ; i++ { ch <- i fmt.Printf("写入数据: %v \n",i) } close(ch) }
|
10.计时器与channel
计时器类型表示单个事件。当计时器过期时,当前时间将被发送到c
上(c是一个只读channel <-chan time.Time,该channel中放入的是Timer结构体
),除非计时器是After()
创建的。计时器必须使用NewTimer()
或After()
创建。
10.1 NewTimer
NewTimer()
创建一个新的计时器,它会在至少持续时间d
之后将当前时间发送到其channel
上。
使用示例:
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(5 * time.Second) fmt.Printf("开始时间 %v \n",time.Now()) out := <- timer.C fmt.Printf("变量out-> 类型: %T 值:%v \n",out,out) fmt.Printf("开始时间 %v \n",time.Now()) }
|
10.2 After
After()
函数相当于NewTimer(d). C
,如下源码:
func After(d Duration) <-chan Time { return NewTimer(d).C }
|
使用示例
package main import ( "fmt" "time" ) func main() { ch := time.After(5 * time.Second) fmt.Printf("开始时间 %v \n",time.Now()) out := <- ch fmt.Printf("变量out-> 类型: %T 值:%v \n",out,out) fmt.Printf("开始时间 %v \n",time.Now()) }
|