引入
在多线程的那篇博客中,我们可以注意点,每一段代码的最后都会有这样一段代码:
var s string
fmt.Scan(&s)
这段代码的含义就是等待输入,那究竟是为什么需要这一段代码呢?
如果去掉了这一段代码,在屏幕上并不会输出任何东西。
因为,这个进程(主线程)创建完两个线程之后就关闭了,同时两个刚刚创建的线程,还得队列里没有运行的线程只能和主线程一起被关闭。
那有没有什么办法使得线程在运行完之后,告诉进程(主线程)它已经运行完了,然后进程(主线程)再关闭呢?
这里就引出了Golang中,协程与协程之间的通信——channel
channel
goroutine之间通过channel来通讯,可以认为channel是一个管道或者先进先出的队列,可以从一个goroutine中向channel发送数据,在另一个goroutine中取出这个值。
定义
var 名字 chan 类型
创建
make(chan 类型,长度)
有长度的channel被称作带缓冲的channel,当然channel也可以不带缓冲,在make中省略长度这一参数。
阻塞
- 默认情况下,channel的读与写都是阻塞的,除非另一端已经准备好了。
- 带缓冲的channel可以不断写入,直到缓冲长度满了才阻塞。
写入(发送)
c:=chan int
c<-1
向c中写入1。
读取(接收)
c:=chan int
i:=<-c
从c中读取,并将这个数据储存到变量i。
关闭
这个并不是一个必须的操作。
如果向一个已经关闭的channel里面写入数据就会造成panic。
如果从一个已经关闭的channel里面读取信息,就会获取到相应类型的0值。
如果再次关闭一个已经关闭的channel,也会造成panic。
超时问题
如果一个协程在某一个channel阻塞太久,系统也是无法检测到的,而且channel自身也是没有定时器的,并不会因为自身阻塞太久而关闭。
而被阻塞的协程以及channel的资源是无法被回收的,最终会造成内存泄漏。
为此,我们可以写一个select来避免这种情况的发生。
func timeout() {
time.Sleep(10000)
chan2 <- true
}
func main() {
chan1 = make(chan int)
chan2 = make(chan bool)
go timeout()
select {
case <-chan1:
case <-chan2:
fmt.Println("chan1长时间为接收到数据")
}
}
最后通过经典的生产者消费者模型来更好地理解channel。