go channel的学习总结

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的滥用

扫描二维码关注公众号,回复: 5560106 查看本文章

关闭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。

总结:channel是一种非常好的通信机制,使用非常灵活,想要继续了解更深的,可以看一下他的实现源码,从而加深认识。不要通过共享内存来通信,而是通过通信来共享内存,要了解阻塞channel的三种方式。

猜你喜欢

转载自blog.csdn.net/LYue123/article/details/88375079