[Go语言] 操作channel时遇到panic怎么办?

很多同学在使用channel时都遇到过这种情况:Panic问题,相信大家对于这种设计也吐槽了不少吧?这篇文章我们就来扒一扒这样设计的初衷。

潜在的Panic主要有两种: 重复close一个channel,向已经closed的channel继续发送消息。

最懒的解决办法就是通过recover来简单粗暴的恢复,可是这就违背了设计者的初衷。对于channel c来说,内置的close函数表明了不会再有任何值发送到c,重复关闭或者向已经关闭的c发送任何消息都会导致runtime panic,关闭nil channel也会导致panic。

这里有个问题,如果c关闭了,继续从c读取消息会怎么样?不用担心,至少不会有panic发生。首先是从c中读取之前发送但是还没有被接收的消息,当这类消息接收完了,之后接受到的消息都是对应channel类型的零值,要注意的是,从一个关闭的channel中接收消息是完全不会阻塞的!!当然,我们可以通过接收操作的第二个参数来判断channel是否已经关闭,如果关闭,就不要继续接收消息了。

回到上面的recover话题,为什么不提议这么做呢,因为一旦这么做,就意味着你对自己的程序设计时存在什么潜在的bug根本就不清楚,只想着通过异常处理这种最粗暴的方式来解决。向一个还在打开的channel发送消息,就是设计上可能存在的bug!

其实从底层来说,close也是channel上的一次消息发送操作,只不过发送的是一个特殊的关闭消息,该消息就是承诺给系统,该channel绝对不会再收到任何消息。如果继续发送消息,就会违背这种承诺。同时,由于close也是消息发送,因此重复close也会导致panic。

大家应该都知道这个idiom:只应该由发送方来关闭channel。那同学们肯定也有疑问,那如果同一个channel有多个发送方呢?这个就是我们程序设计的问题了,如果多个发送方都要求去关闭channel,但是彼此之间根本就不沟通,那就是有问题的:因为这种情况下,如果某个发送方要关闭channel,却不通知其它发送方,那就存在很大的潜在bug了,至少关闭一个公有频道,需要得到大家的认可才行!

这里附上Rob Pike大神的一段原文翻译:

关闭一个channel就是释放一个资源。多次关闭一个channel就像多次关闭文件描述符、多次释放内存块一样,都是没有任何意义的。这些操作都意味着代码是有问题的,这也是为什么我们设计时就强制产生了panic。


看看,大神果然是大神,一句话就阐述清了问题:都是设计和代码问题!因此让自己的设计和代码清晰明朗起来是非常重要的,能不用recover解决问题就不用。

总之,Go的设计原则就是简洁、清晰、不冗余,用一句流行语来说:我的代码,我做主







### Go语言Channel的使用教程 #### 创建通道 在Go语言里,`make()`函数用来创建通道。语法如下所示: ```go ch := make(chan int) ``` 这行代码创建了一个整型的无缓冲通道[^1]。 #### 发送和接收数据 向通道发送数据使用的符号是箭头`->`放在通道变量之后,而接收则是在通道前加上 `<-` 。下面是一个简单的例子来展示如何在一个goroutine中发送,在另一个goroutine中接收。 ```go func main() { ch := make(chan string) go func() { ch <- "hello" }() msg := <-ch fmt.Println(msg) } ``` #### 关闭通道 当不再需要继续通过某个特定方向传送消息可以关闭该通道。通常情况下由生产者负责关闭通道。一旦被关闭,则不能再往里面发消息,但是仍然可以从已有的未读取消息队列中获取信息直到全部取出为止。 ```go close(ch) ``` #### 缓冲通道 除了默认的无缓冲通道外,还可以指定大小初始化带缓冲区版本的通道。这样即使没有其他goroutines准备立即处理新传入的数据项也不会造成阻塞现象发生。 ```go bufferedCh := make(chan int, 3) // 定义长度为3的缓冲chan ``` #### 非阻塞操作 利用内置的选择语句 `select {}`, 可以实现非阻塞式的收发行为。如果没有任何case能够执行的话就会触发default分支下的逻辑部分。 ```go select { case v := <-ch: fmt.Printf("received %v\n", v) default: fmt.Println("no message received") } ``` ### 常见问题解答 有可能会遇到尝试对nil类型的通道进行操作的情况,这是不允许的行为并会引发panic错误。因此务必记得总是要先调用`make()`完成实例化再做进一步的动作[^2]。 另外需要注意的是关于同步与异步的区别:对于不含有任何内部存储空间(即容量设为了零)的一类对象而言它们属于完全同步性质;而对于那些拥有一定数量槽位可供临存放待交换的信息单元来说则是有条件性的——取决于当前剩余可用位置的数量多少决定着是否允许立刻提交或者提取请求而不必等待对方响应[^5]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值