引文
我对他说,走过去吧,那里树叶会向你招手,石头会向你微笑,河水会向你问候。那里没有贫贱也没有富贵,没有悲伤也没有疼痛,没有仇也没有恨......那里人人死而平等。 —— 《第七天》
前言
在 Golang
中谈起 channel
,就不可能避免说到并发编程,在 Golang
中谈到并发编程,那不得不提起 另外一个名字 goroutines
,channel
和 goroutines
就是 Golang
中并发编程的核心。Go中并发编程方式是将共享值在通道上传递,不由单独的执行线程主动共享
,在任何给定的时间只有一个 goroutine
可以访问该值。在 Golang
中将此简言之不通过共享内存进行通信,要通过通信共享内存
。
channel 是什么?
Go语言
中的 channel
是一种特殊的类型,遵循先入先出(First In First Out
)的规则,保证收发数据的有序性。从字面意思上看,大概就是管道的意思。可以想象 channel
是一个传送管道,将 goroutine
想象成管道两边的人,一个从一边将物品放入管道,另一个人在另一边取出。图解示意:
channel 的简单使用
如 map 一样,channel
也是使用 make
关键字来创建充当对底层数据结构的引用。
ch1 := make(chan struct{}) // 无缓冲 channel
ch2 := make(chan struct{},0) // 无缓冲 channel
ch3 := make(chan struct{}, 10) // 带缓冲 channel
在 make 创建时有一个可选的整数参数,它会设置 channel
的缓冲区大小。对于无缓冲或同步通道,默认值为0。
在这里,channel 又被分为了 unbuffered channel
和 buffered channel
,也就是无缓冲区channel和带缓冲区channel。
unbuffered channel & buffered channel
unbuffered channel
就是缓冲大小为 0 的 channel
,无缓冲区的 channel 本身是不存放数据的,在发送和接收都会被阻塞。也就是相当于,你现在是一个 send
身份,但是当另外一个没有 receive
你发送的值之前,你一直处于阻塞(等待接收)状态;反过来说,你现在是一个 receive
身份,你就会一直阻塞(等待发送)状态,在你拿到值之前,你会一直等待。
无缓冲区通道
来看下面这段代码:
func main() {
ch := make(chan struct{})
ch <- struct{}{}
fmt.Println("send a struct")
}
这段代码执行时候会出现一个 deadlock
错误,死锁!为什么呢?
其实这里是因为 ch
是一个无缓冲区的 channel,unbuffered channel 只有在有 接受方
的时候才能发送成功。现在就好比,你伸手递出去一个物品,但是永远没有人从你手中接收这个物品。
所以想解决这个问题,只需要提供一个 接收者
就行了,修改代码为:
func main() {
ch := make(chan struct{})
go func() {
defer close(ch)
v := <-ch
fmt.Printf("receive a struct: %v\n", v)
}()
ch <- struct{}{}
fmt.Println("send a struct")
}
这时候数据发送成功,也能在另外一个 goroutine 里获取值,但是可以发现,这两个 goroutine 之间通信的数据信息是同步化的。因此,无缓冲通道也可以称为同步通道。
带缓冲区通道
先看代码:
func main() {
ch := make(chan struct{}, 1)
ch <- struct{}{}
fmt.Println("send a struct")
}
在带缓冲区通道中,直接往 channel 中发送不超出大小的数据量,就不会出现 deadlock
错误。
那么我们对代码进行修改:
func main() {
ch := make(chan struct{}, 1)
ch <- struct{}{}
ch <- struct{}{}
fmt.Println("send a struct")
}
这时候,就会出现 deadlock
错误,因为在这个 channel 中缓冲大小为1,第一次发送没有被接收,就占满了channel的缓冲区,再对一个满的 channel 进行数据发送时,也会出现deadlock
错误。
继续对代码进行修改:
func main() {
ch := make(chan int, 1)
go func() {
defer close(ch)
defer fmt.Println("我关闭了")
ch <- 1
ch <- 2
fmt.Println("send a struct")
}()
time.Sleep(5 * time.Second)
for {
v, ok := <-ch
if !ok {
fmt.Println("channel closed")
break
}
fmt.Printf("received a struct %s\n", v)
}
}
它的执行结果是:
~ 等待5s
received a struct %!s(int=1)
received a struct %!s(int=2)
send a struct
我关闭了
channel closed
可以看到 我关闭了
在打印完接收后执行了,所以这个 ch 在缓冲区满后,被阻塞了,直到接收了第一个值,然后继续执行后面的代码。其实这里有个问题,如果我们将缓冲区设置大一点,他执行完后执行关闭了,接收值会有影响吗?
// 修改
ch := make(chan int, 2)
此时,执行结果为:
send a struct
我关闭了
~ 等待5s
received a struct %!s(int=1)
received a struct %!s(int=2)
channel closed
这里可以直到,channel 关闭后,对 range 其实并不影响。
总结
- 不通过共享内存进行通信,要通过通信共享内存
- channel 分为 有缓冲区 和 无缓冲区的
- 向一个满 channel 中发送数据会 deadlock,向一个空的 channel 中读取数据会 deadlock
- 当 channel 被关闭后,依然可以读取缓冲区内的值