channel
继续肝channel相关知识!
channel基本用法
要用就的先声明:
普通int类型声明:
var v2 int
类型为int的channel声明:
var v2 chan int
声明一个map,元素是bool型的channel:
var m map[string] chan bool
定义一个channel,直接使用内置的函数make()即可:
ch := make(chan int)
在channel的用法,最常见的包括写入和读出。将一个数据写入(发送)至channel的语法,如下:
ch <- value
如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,直到channel中被写入数据为止,这有点像阻塞IO,接收数据一直等着,知道有数据准备好,我再接收数据。
select
在Unix中,通过调用select()函数来监控一系列的文件句柄,一旦其中一个句柄发生IO操作,该select调用就会被返回,该机制也用于实现高并发的Socket服务程序,Go语言直接在语言级别支持select关键字,用于处理异步IO问题。select的用法和switch语言类似,由select开始一个新的选择块,每个选择块的语句由case语句描述,select语句中限制了每个case语句必须有一个IO操作,如下:
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
select不像switch,后面并不带判断条件,而是直接去查看case语句。每个case语句都必须是一个面向channel的操作。
缓冲机制
不带缓冲机制的channel,这种场景是在单个数据传递中可以接受,但由于现实中有很多高并发的场景,对于持续大量数据的传输channel需要缓冲,达到像消息队列一样的作用,达到写数据和取数据异步。需要创建带缓冲的channel如下:
a := make(chan int, 1024)
在调用make方法的时候多传入一个参数表示缓冲的大小,比如上文创建了一个大小是1024的int类型channel,写入的时候直到缓冲没有被填满之前都不会被阻塞。
从带缓冲的channel中读取数据可以使用与常规非缓冲channel完全一致的方法,但也可以使用range关键来实现更为简便的循环读取。如下:
for i := range c {
fmt.Println("Received:", i)
}
超时机制
在并发编程的通信过程中,需要考虑超时问题,即向channel写数据时发现channel已满,或者从channel试图读取数据时发现channel为空。如果不正确处理这些情况,很可能会导致整个goroutine锁死。总的来说解决这种问题就是用一个参数设置超时的时间,一旦过了超时时间就释放掉资源,或者重试,重试再超时就立即终止并返回超时信息time out。
Go语言没有提供超时的机制,但可以利用select机制。因为select的特点是只要其中一个case已经完成,程序就会继续往下执行,而不会考虑其他case的情况,基于此特性,为channel实现超时机制如下:
// 首先,我们实现并执行一个匿名的超时等待函数
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // 等待1秒钟
timeout <- true
}()
// 做选择
select {
case <-ch:
// 从ch中读取到数据
case <-timeout:
// 一直没有从ch中读取到数据,但从timeout中读取到了数据
}
意思一旦超时我们走超时的case条件下的业务,返回超时的数据,这种写法看起来是一个小技巧,但却是在Go语言开发中避免channel通信超时的最有效方法。在实际的开发过程中,这种写法也需要被合理利用起来,从而有效地提高代码质量。
channel的传递
在Go语言中channel本身也是一个原生类型,与map之类的类型地位一样,因此channel本身在定义后也可以通过channel来传递。可以利用channel可被传递的特性来实现我们的管道,假设我们传递的数据是一个整型
type PipeData struct {
value int
handler func(int) int
next chan int
}
然后写一个常规的处理函数。只要定义一系列PipeData的数据结构并一起传递给这个函数,就可以达到流式处理数据的目的。如下:
func handle(queue chan *PipeData) {
for data := range queue {
data.next <- data.handler(data.value)
}
}
单向channel
单向channel只能用于发送或者接收数据。channel本身必然是同时支持读写的,否则根本没法用。假如一个channel真的只能读,那么肯定只会是空的,因为你没机会往里面写数据。同理,如果一个channel只允许写,即使写进去了,也没有丝毫意义,因为没有机会读取里面的数据。所谓的单向channel概念,其实只是对channel的一种使用限制。比如只能往这个channel写,或者只能从这个channel读。举例如下:
var ch1 chan int // ch1是一个正常的channel,不是单向的
var ch2 chan<- int// ch2是单向channel,只用于写int数据
var ch3 <-chan int // ch3是单向channel,只用于读取int数据
单向channel的意义就是多了一种channel用法,更多的是起到一种契约作用。
关闭channel
关闭:
close(ch)
判断是否关闭:
x, ok := <-ch
如果返回值是false则表示ch已经被关闭。
备注
先这样吧!channel传递还需要再理解下!
本文正在参与「掘金Golang主题学习月」, 点击查看活动详情。