Channel
Channel 是 Go 中为 goroutine 提供的一种通信机制,借助于 channel 不同的 goroutine 之间可以相互通信。
channel 是有类型的,而且有方向,可以把 channel 类比成 unix 中的 pipe。Go 通过 <- 操作符来实现 channel
的写和读,send value <- 在 channel 右侧,receive value <- 在左侧,receive value 不赋值给任何变量是合法的。
类型
i := make(chan int)//int 类型
s := make(chan string)//字符串类型
r := make(<-chan bool)//只读
w := make(chan<- []int)//只写
<- w //合法的语句
Channel 最重要的作用就是传递消息。
package main
import (
"fmt"
)
func main() {
c := make(chan int)
defter close(c)
go func() {
fmt.Println("goroutine message")
c <- 1 // #1
}()
<-c // #2
fmt.Println("main function message")
}
例子中声明了一个 int 类型的 channel,在 goroutine 中在代码 #1 处向 channel 发送了数据 1 ,在 main 中 #2 处等待数据的接收,如果 c 中没有数据,代码的执行将发生阻塞,直到有 goroutine 开始往 c 中 send value。 这是 channel 最简单的用法之一:同步,这种类型的 channel 容量是 0,称之为 unbuffered channel。
Unbuffered channel
Channel 可以设置容量,表示 channel 允许接收的消息个数,默认的 channel 容量是 0 称为 unbuffered
channel ,对 unbuffered channel 执行 读 操作 value := <-ch 会一直阻塞直到有数据可接收,执行 写 操作 ch
<- value 也会一直阻塞直到有 goroutine 对 channel 开始执行接收,正因为如此在同一个 goroutine 中使用
unbuffered channel 会造成 deadlock。
package main
import (
"fmt"
)
func main() {
c := make(chan int)
c <- 1
<-c
fmt.Println("main function message")
}
执行报 fatal error: all goroutines are asleep - deadlock! ,读和写相互等待对方从而导致死锁发生。
Buffered channel
如果 channel 的容量不是 0,此类 channel 称之为 buffered channel ,buffered channel 在消息写入个数 未达到容量的上限之前不会阻塞 ,一旦写入消息个数超过上限,下次输入将会阻塞,直到 channel 有位置可以再写入。
image.png
package main
import (
"fmt"
)
func main() {
c := make(chan int, 3)
go func() {
for i := 0; i < 4; i++ {
c <- i
fmt.Println("write to c ", i)
}
}()
for i := 0; i < 4; i++ {
fmt.Println("reading", <-c)
}
}
上面的例子会输出:
write to c 0
reading 0
write to c 1
reading 1
write to c 2
reading 2
write to c 3
reading 3
根据上文对 buffered channel 的解释,这个例子中 channel c 的容量是 3,在写入消息个数不超过 3 时不会阻塞,输出应该是:
write to c 0
write to c 1
write to c 2
reading 0
reading 1
reading 2
write to c 3
reading 3
问题在哪里?问题其实是在 fmt.Println ,一次输出就导致 goroutine 的执行发生了切换(相当于发生了 IO 阻塞),因而即使 c 没有发生阻塞 goroutine 也会让出执行,一起来验证一下这个问题。
package main
import (
"fmt"
"strconv"
)
func main() {
c := make(chan int, 3)
s := make([]string, 8)
var num int = 0
go func() {
for i := 0; i < 4; i++ {
c <- i
num++
v := "inner=>" + strconv.Itoa(num)
s = append(s, v)
}
}()
for i := 0; i < 4; i++ {
<-c
num++
v := "outer=>" + strconv.Itoa(num)
s = append(s, v)
}
fmt.Println(s)
}
这里创建了一个 slice 用来保存 c 进行写入和读取时的执行顺序,num 是用来标识执行顺序的,在没有加入 Println 之前,最终 s 是 [inner=>1 inner=>2 inner=>3 inner=>4 outer=>5 outer=>6 outer=>7 outer=>8] ,输出结果表明 c 达到容量上线之后才会发生阻塞。
相反有输出语句的版本结果则不同:
package main
import (
"fmt"
"strconv"
)
func main() {
c := make(chan int, 3)
s := make([]string, 8)
var num int = 0
go func() {
for i := 0; i < 4; i++ {
c <- i
num++
v := "inner=>" + strconv.Itoa(num)
s = append(s, v)
fmt.Println("write to c ", i)
}
}()
for i := 0; i < 4; i++ {
num++
v := "outer=>" + strconv.Itoa(num)
s = append(s, v)
fmt.Println("reading", <-c)
}
fmt.Println(s)
}
[outer=>1 inner=>2 outer=>3 inner=>4 inner=>5 inner=>6 outer=>7 outer=>8] 输出结果能表明两个 goroutine 是交替执行,也就是说 IO 的调用 Println 导致 goroutine 的让出了执行。
使用 select 读取多个 channel
Go 提供了 select 语句来处理多个 channel 的消息读取。
package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for {
c1 <- "from 1"
time.Sleep(time.Second * 2)
}
}()
go func() {
for {
c2 <- "from 2"
time.Sleep(time.Second * 2)
}
}()
go func() {
for {
select {
case msg1 := <-c1:
fmt.Println(msg1)
case msg2 := <-c2:
fmt.Println(msg2)
}
}
}()
var input string
fmt.Scanln(&input)
}
select 语句可以从多个可读的 channel 中随机选取一个执行,注意是 随机选取。
关闭 Channel
Channel 可以被关闭 close ,channel 关闭之后仍然可以读取,但是向被关闭的 channel send 会 panic。如果 channel 关闭之前有值写入,关闭之后将依次读取 channel 中的消息,读完完毕之后再次读取将会返回 channel 的类型的 zero value:
package main
import (
"fmt"
)
func main() {
c := make(chan int, 3)
go func() {
c <- 1
c <- 2
c <- 3
close(c)
}()
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
}
输出 1 2 3 0 0 0 ,0 是 int channel c 的 zero value。
package main
import (
"fmt"
)
func main() {
c := make(chan int, 3)
go func() {
c <- 1
c <- 2
c <- 3
close(c)
}()
for i := range c {
fmt.Println(i)
}
}
c 可以进行 range 迭代,如果 channel 没有被关闭 range 会一直等待 channel,但是关闭 channel 之后可以隐式的中断 range 的迭代
判断 channel 的关闭
Go 提供了 ok 表达式来判断 channel 的关闭状态。
value, ok <- c
如果 channel 是关闭状态,ok 是 false,value 是 channel 的 zero value,否则 ok 是 true 表示 channel 未关闭,value 表示 channel 中的值。
网友评论