写在文章开头
go语言
提供了各种非常方便的语法糖,使得我们实现用最少的语法做尽可能高效的事情,而本文就简单介绍如何实现非阻塞处理多个channel
,希望对你有帮助。
Hi,我是 sharkChili ,是个不断在硬核技术上作死的技术人,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。
详解channel非阻塞技巧
假设我们现在有这样一个需求,有个有缓冲区bufferedChannel
和无缓冲区noBufferChannel
,我们希望用一个协程做到查看无缓冲区channel
没数据时,再处理有缓冲区channel
。
对此,我们先介绍一下select
语法,按照其工作原理可知,只要某个case
无法满足要求就会走到下一个case
,由此我们不妨做个猜测,由此我们是否可以将channel的收发作为case分支做到分支1的channel
无法处理时执行分支2逻辑呢?
对此我们给出这样一段代码,可以看到笔者声明了有缓冲区channel1和无缓冲区chann2,然后向有缓冲区chan1投递数据,进而走到select分支。
重点来了,按照正常逻辑,noBufferChannel
是无缓冲区,投递数据1应该阻塞等待消费才对,但是在select分支语法下,在noBufferChannel
数据投递失败之后,走到了下一个分支,该分支的channel收到上文投递的数据1,直接步入逻辑完成打印:
func main() {
//创建两个channel
noBufferChannel := make(chan int)
bufferedChannel := make(chan int, 1)
//往有缓冲区channel中投递数据,因为有缓冲区,数据投递到缓冲区后,逻辑继续向下
bufferedChannel <- 1
//通过select语法避免无缓冲区chan2数据没有被及时消费而阻塞
select {
case noBufferChannel <- 1:
log.Println("无缓冲区channel逻辑执行了")
case num := <-bufferedChannel:
log.Println("有缓冲区channel收到数据了:", num)
default:
log.Println("defult logic")
}
}
对应输出结果如下:
有缓冲区channel收到数据了: 1
由此我们可知,通过select语句,可以非阻塞的执行每一个case逻辑,以本文代码为例,无缓冲区channel
的case
逻辑发现数据无法投递时,则走到下一个分支查看是否可以执行,结果有缓冲区channel
有数据,最终非阻塞走到分支2完成逻辑执行:
实际上,go语言做到更强大的优化,假如上文两个channel的逻辑都无法执行且没有default分支,它会将这个协程分别注册到所有channel
的阻塞队列中,只要某个channel
具备处理条件,就会唤醒对应分支。
对应的我们给出这样一个select实现非阻塞轮询的例子,子协程非阻塞轮询所有channel,发现channel都不具备处理的条件,于是将该协程存到各个channel的阻塞队列中。随后主协程消费无缓冲区队列的数据,唤醒无缓冲区的case逻辑:
对应我们给出示例代码:
func main() {
var wg sync.WaitGroup
wg.Add(1)
//创建两个channel
noBufferChannel := make(chan int)
bufferedChannel := make(chan int, 1)
go func() {
log.Println("子协程执行,发现无法执行后阻塞")
defer wg.Done()
//通过select语法避免无缓冲区chan2数据没有被及时消费而阻塞
select {
case noBufferChannel <- 1:
log.Println("无缓冲区channel逻辑执行了")
case num := <-bufferedChannel:
log.Println("有缓冲区channel收到数据了:", num)
}
}()
//休眠1s等待协程1执行完成
time.Sleep(1 * time.Second)
log.Println("主协程消费数据")
num := <-noBufferChannel
wg.Wait()
log.Println("执行结束,num:", num)
}
小结
以上便是笔者对于go
语言channel
非阻塞的工作机制和使用分析,希望对你有帮助。
我是 sharkchili ,CSDN Java 领域博客专家,mini-redis的作者,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。