Golang通道阻塞情况与通道无阻塞实现

news/2025/3/16 18:30:56/

个人博客

一、通道阻塞原理

在Go语言中,通道会在以下情况下发生阻塞:

  1. 如果通道已满,并且没有协程在读取通道中的数据,那么任何试图将数据写入通道的协程都会被阻塞,直到有空间可用为止。
  2. 如果通道为空,并且没有协程在等待从通道中读取数据,那么任何试图从通道中读取数据的协程都会被阻塞,直到有数据可用为止。

二、通道阻塞场景

在channel中,无论是有缓存通道、无缓冲通道都存在阻塞的情况。阻塞场景共4个,有缓存和无缓冲各2个。

2.1 无缓冲通道

无缓冲通道的特点是,发送的数据需要被读取后,发送才会完成,它阻塞场景

  1. 通道中无数据,但执行读通道。
  2. 通道中无数据,向通道写数据,但无协程读取。
// 场景1
func ReadNoDataFromNoBufCh() {noBufCh := make(chan int)<-noBufChfmt.Println("read from no buffer channel success")// Output:// fatal error: all goroutines are asleep - deadlock!
}// 场景2
func WriteNoBufCh() {ch := make(chan int)ch <- 1fmt.Println("write success no block")// Output:// fatal error: all goroutines are asleep - deadlock!
}

注:示例代码中的Output注释代表函数的执行结果

每一个函数都由于阻塞在通道操作而无法继续向下执行,最后报了死锁错误。

2.2 有缓存通道

有缓存通道的特点是,有缓存时可以向通道中写入数据后直接返回,缓存中有数据时可以从通道中读到数据直接返回,这时有缓存通道是不会阻塞的,它阻塞场景是

  1. 通道的缓存无数据,但执行读通道。
  2. 通道的缓存已经占满,向通道写数据,但无协程读。
// 场景1
func ReadNoDataFromBufCh() {bufCh := make(chan int, 1)<-bufChfmt.Println("read from no buffer channel success")// Output:// fatal error: all goroutines are asleep - deadlock!
}// 场景2
func WriteBufChButFull() {ch := make(chan int, 1)// make ch fullch <- 100ch <- 1fmt.Println("write success no block")// Output:// fatal error: all goroutines are asleep - deadlock!
}

三、通道无阻塞读写

3.1 Select实现无阻塞读写

下面示例代码是使用select修改后的无缓冲通道和有缓冲通道的读写,以下函数可以直接通过main函数调用;

// 1.select结构实现通道读
func ReadWithSelect(ch chan int) (x int, err error) {select {case x = <-ch:return x, nildefault:return 0, errors.New("channel has no data")}
}// 无缓冲通道读
func ReadNoDataFromNoBufChWithSelect() {bufCh := make(chan int)if v, err := ReadWithSelect(bufCh); err != nil {fmt.Println(err)} else {fmt.Printf("read: %d\n", v)}// Output:// channel has no data
}// 有缓冲通道读
func ReadNoDataFromBufChWithSelect() {bufCh := make(chan int, 1)if v, err := ReadWithSelect(bufCh); err != nil {fmt.Println(err)} else {fmt.Printf("read: %d\n", v)}// Output:// channel has no data
}// 2. select结构实现通道写
func WriteChWithSelect(ch chan int) error {select {case ch <- 1:return nildefault:return errors.New("channel blocked, can not write")}
}// 无缓冲通道写
func WriteNoBufChWithSelect() {ch := make(chan int)if err := WriteChWithSelect(ch); err != nil {fmt.Println(err)} else {fmt.Println("write success")}// Output:// channel blocked, can not write
}// 有缓冲通道写
func WriteBufChButFullWithSelect() {ch := make(chan int, 1)// make ch fullch <- 100if err := WriteChWithSelect(ch); err != nil {fmt.Println(err)} else {fmt.Println("write success")}// Output:// channel blocked, can not write
}

注:示例代码中的Output注释代表函数的执行结果

从结果能看出,在通道不可读或者不可写的时候,不再阻塞等待,而是直接返回。

3.2 使用Select+超时改善无阻塞读写

使用default实现的无阻塞通道阻塞有一个缺陷:当通道不可读或写的时候,会即可返回。实际场景,更多的需求是,我们希望尝试读一会数据,或者尝试写一会数据,如果实在没法读写再返回,程序继续做其它的事情。

使用定时器替代default可以解决这个问题,给通道增加读写数据的容忍时间,如果500ms内无法读写,就即刻返回。示例代码修改一下会是这样:

func ReadWithSelect(ch chan int) (x int, err error) {timeout := time.NewTimer(time.Microsecond * 500)select {case x = <-ch:return x, nilcase <-timeout.C:return 0, errors.New("read time out")}
}func WriteChWithSelect(ch chan int) error {timeout := time.NewTimer(time.Microsecond * 500)select {case ch <- 1:return nilcase <-timeout.C:return errors.New("write time out")}
}

结果就会变成超时返回:

read time out
write time out
read time out
write time out

四、总结

本篇文章介绍了在Go语言中,通道会在以下情况下发生阻塞:

  1. 如果通道已满,并且没有协程在读取通道中的数据,那么任何试图将数据写入通道的协程都会被阻塞,直到有空间可用为止。
  2. 如果通道为空,并且没有协程在等待从通道中读取数据,那么任何试图从通道中读取数据的协程都会被阻塞,直到有数据可用为止。

以及解决阻塞的2种办法:

  1. 使用select的default语句,在channel不可读写时,即可返回
  2. 使用select+定时器,在超时时间内,channel不可读写,则返回

五、参考链接

  1. 大彬,http://lessisbetter.site/2018/11/03/Golang-channel-read-and-write-without-blocking/
  2. https://studygolang.com/articles/6024

http://www.ppmy.cn/news/151602.html

相关文章

佳能ts9020墨盒不识别_感动常在!佳能ts9020打印机更换墨盒经验

感动常在&#xff01;佳能ts9020打印机更换墨盒经验 2017-12-15 18:21:36 5点赞 14收藏 2评论 很多人因为在张大妈这里推荐买了一台海淘打印机ts9020&#xff0c;本人也买了&#xff0c;曲折的海淘经历上一篇已经叙述&#xff0c;今天我给大家来说说墨盒的问题&#xff0c;算是…

佳能打印机扫描文件到电脑显示设置计算机,佳能打印机办公用哪个型号好 佳能打印机扫描文件到电脑步骤【详解】...

打印机 是现代办公的必备工具&#xff0c;随着科技的进步和发展&#xff0c;打印机的功能更加的多样化&#xff0c;而且体积也是越来越小。在众多品牌的打印机中&#xff0c;佳能是一个非常不错的品牌&#xff0c;那么 佳能打印机办公用哪个型号好 呢&#xff1f;在使用的过程中…

佳能MG3680彩版喷墨打印机AP模式开启/关闭方法

【开启】 确认电源已开启 > 按住Wi-Fi按钮&#xff0c;并在电源指示灯闪烁时松开 > 依次按黑白按钮 > 彩色按钮以及Wi-Fi按钮 > 电源指示灯由闪烁变为亮起&#xff0c;并且AP模式将会启用。 ※初始密码为机身号 【关闭】 按住Wi-Fi按钮&#xff0c;并在电源指示灯…

佳能g3800编程器固件_佳能g3800清零软件 佳能g3800打印机废墨清零

佳能g3800清零软件是一款专门用来对佳能g3800打印机进行废墨清零的软件,假如您使用的是佳能g3800打印机遇到了墨水计数器满了等问题,说明需要对其进行清零了。该g3800清零软件可以清除掉打印机的废墨,让打印机正常打印。 界面预览图: 佳能G3800一体机是一个集成了扫描功能的…

在openSUSE-Leap-15.4-DVD-x86_64中使用佳能喷墨打印机ip2780

在openSUSE-Leap-15.4-DVD-x86_64中使用佳能喷墨打印机ip2780 实际问题描述&#xff1a;本人在2011年购买佳能喷墨打印机ip2780&#xff0c;平时是在windows 7系统中使用的&#xff0c;佳能官网有提供linux驱动的&#xff0c;偶尔在fedora16系统中使用&#xff0c;彩色墨盒早已…

佳能喷墨打印机设置连接wifi TS308

2019独角兽企业重金招聘Python工程师标准>>> 最近买了一台便宜的佳能喷墨打印机:佳能ts308,设置真是很不方便,今天说说佳能打印机的设置经过 包含电脑和手机两种连接方法 我的经过如下: 手机下载佳能打印app,安装好; 按下了打印机的直连按钮,在app里选直连,但是必须…

算法与数据结构(三)——排序算法大总结

六大排序算法&#xff1a;插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序 一、插入排序二、选择排序三、冒泡排序四、归并排序 一、插入排序 1.从第一个元素开始&#xff0c;该元素可以认为已经被排序 2.取下一个元素tem&#xff0c;从已排序的元素序列从后往前扫…

Lombok 的正确使用姿势

文章目录 1.Lombok 是什么2.安装 Lombok3.Spring Boot 集成 Lombok4.使用 Lombok4.1 注解一览表4.2 部分使用介绍Getter(lazytrue)ValueBuilderSuperBuilderSneakyThrowsSlf4jCleanupNonNullWithSynchronizedval 在 Java 开发领域中&#xff0c;Lombok 插件已经成为一个非常流行…