go channel
channel是goland中一种特殊的类型的存在,对于goland的学习来说是非常重要的。
channel是goland中goroutine间的通信的一种方式,可以类比成 Unix 中的进程的通信方式管道。
并发模型
因为goland是一个支持并发的编程语言,而且channel又在并发中起了关键作用,所以我们简单介绍一下并发模型,传统的并发模型主要是Actor和GSP两种,这里我们主要介绍GSP模型,因为goland的并发模型就是参考的GSP,GSP英文全称:communicating sequential processes,GSP模型由并发执行实体和消息通道组成,并发执行实体就是进程,线程或者协程,实体之间通过消息通道发送消息,接收消息进行通信;和Actor模型不同的是,GSP模型更看重发送消息的载体,即消息通道,而在goland中并发执行实体就是goroutine,消息通道就是channel。
channel介绍
就像上面说的channel提供了一种通信机制,两个或者多个goroutine之间可以通过channel进行通信(共享内存),channel本身关联了一种数据类型,即在创建channel的时候需要指定channel所要承载的数据类型。
阻塞channel的三种方式:
1,向未初始化的channel中读写数据,会造成永久阻塞
2,向缓存区已满的channel中写入数据,会造成暂时阻塞
3,向没有数据的channel中读数据,会造成暂时阻塞
channel的创建
channel使用内置的make函数创建,make中包括两个参数,第一个即类型,然后是容量
ch:=make(chan int)
如果像上面一样定义的话,就是无缓存通道,若加一个参数n,就代表是一个带有n个缓存的通道
和map,slice类似,make创建了一个对底层数据结构的引用,当赋值或者参数传递时是拷贝了一个channel的引用,他们指向同一个底层数据结构,和其他引用类型一样,channel 的空值为 nil 。使用 == 可以对类型相同的 channel 进行比较,只有指向相同对象或同为 nil 时,才返回 true
channel的读写操作
//创建一个channel
ch :=make(chan int)
//向channel中发送一个数据
ch <- x
//read
x<-ch
x:=<-ch
我们必须明确读写数据的格式,因为后面还会涉及到只读channel,和只写channel,必须能够区分开
单向channel
我们简单介绍一下就好,很好理解,
ch:=make(<-chan int)
ch1:=make(chan<- int)
如上定义的,ch就是一个只读channel,我们不能向ch中写数据,而ch1就是一个只写channel,我们只能向channel中写数据,其实用channel就能完成他们的功能,只是有时候我们需要防止对channel的滥用
关闭channel
这里不是重点介绍如何关闭channel,相信大家都知道,通过内置函数close()进行关闭,我们要介绍的是一些注意事项
有关 channel 的关闭,你需要注意以下事项:
1,关闭一个未初始化(nil) 的 channel 会产生 panic
2,重复关闭同一个 channel 会产生 panic
3,向一个已关闭的 channel 中发送消息会产生 panic
4,从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭
5,关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
func main(){
ch:=make(chan int,2)
ch<-1
ch<-2
close(ch)
for k:=range ch{
fmt.Println(k)
}
k,ok:=<-ch
fmt.Println(k,ok)
}
C:\Users\my\go\src\hello\learn>go run test2.go
1
2
0 false
channel的用法
阻塞主goroutine
有时候在main中发起一个go可能还没结束,主协程已经结束了,所以可以采用ch来阻塞main goroutine
func main(){
nums:=[]int(0,1,2,3,4,5)
ch:=make(chan int)
go sum(nums,ch)
<-ch
}
func sum(nums []int,ch chan int){
sum:=0
for _,k:=range nums{
sum+=k
}
ch <-sum
}
Range遍历channel
ch := make(chan int, 10)
for x := range ch{
fmt.Println(x)
}
等价于:
for {
x, ok := <- ch
if !ok {
break
}
fmt.Println(x)
}
其中ok字段是一个bool型,读取成功则返回true
配合select使用
select可以同时监听多个channel,
select {
case <- ch1:
...
case <- ch2:
...
case ch3 <- 10;
...
default:
...
}
1 select 可以同时监听多个 channel 的写入或读取
2 执行 select 时,若只有一个 case 通过(不阻塞),则执行这个 case 块
3 若有多个 case 通过,则随机挑选一个 case 执行
4 若所有 case 均阻塞,且定义了 default 模块,则执行 default 模块。若未定义 default 模块,则 select 语句阻5 塞,直到有 case 被唤醒。
6 使用 break 会跳出 select 块。
设置超时时间
ch := make(chan struct{})
// finish task while send msg to ch
go doTask(ch)
timeout := time.After(5 * time.Second)
select {
case <- ch:
fmt.Println("task finished.")
case <- timeout:
fmt.Println("task timeout.")
}
time.After返回的就是一个channel,当时间超过设定时间时,timeout中就会有值,即为超时时间
当然我们也可以设定一个停止channel,即当满足一个条件时向channel中写入一个信息,当读取到这个channel中的信息时,结束select。