手把手教你如何用Go语言编写干净的代码,实际goroutine代码示例

本文发表于入职啦(公众号: ruzhila) 大家可以访问入职啦学习更多的编程实战。

在这里插入图片描述

任何时候,写精简的代码都是对自己的一种挑战,也是对自己的一种提升

案例背景

今天群里有人发了一段代码,让大家看看这段代码有什么问题:

在这里插入图片描述

这个代码的功能是希望在指定的时间内得到ntp服务器的时间,如果超时,就返回一个错误。

因为ntp查询可能会比较长时间,所以这里使用了启动一个goroutine来执行query,然后通过chan来通知主goroutine;

主goroutine会等待返回结果,并且启动一个定时器,如果超时,就返回一个错误

问题分析

其实这个代码是有问题,我一开始也没注意到,就关注了代码是否写得精简,所以我很快给出了一个解决方案:

通过查看github.com/beevik/ntp的代码,除了Query,还提供了一个高级接口:QueryWithOptions:

在这里插入图片描述

也就是默认情况下,不需要你自己处理超时的问题,这个库已经帮你处理好了,所以我们可以直接使用这个接口,我的建议代码如下:

  ntp.QueryWithOptions(ntpServer, ntp.QueryOptions{Timeout: 1 * time.Second})

那么这样的情况,就完全不需要启动一个协程去处理超时的问题了,这样代码就变得更加简洁了。

真正的问题

我以为这样就可以了,后来群里的高手提醒说goroutine泄露,之前代码goroutine没有被正确的关闭,这个时候我才意识到,这个问题的根本原因是因为goroutine没有被正确的关闭。

问题就出在第6行:

ntpQState = make(chan bool)

这个ntpQState是一个无缓冲的chan, 如果没有缓存的情况下,会导致发送方一直阻塞等待,那么当出现超时之后,还没结果的情况下,第19行执行了超时逻辑之后,离开这个函数

这时候即便goroutine执行结束之后,在第1113行的情况下,都会导致这个操作呗阻塞,导致了goroutine泄露。

非常重要的一个结论:
当你用定时器+chan的方式去做超时处理的时候,一定要记得在超时之后,关闭chan,或者预留一个buffer,不要让发送方一直阻塞等待

解决方案

很快群里的人发了一个解决方案:

在这里插入图片描述

针对发送者可能会出现因为没有缓冲导致block, golang也有一个内置方案,就是判断是否有缓冲:

select {
case ntpQState <- true:
default:
}

当没有缓冲的时候就可以解决这个问题了, 所以导致goroutine泄露的问题就解决了

开始精简代码

明白chan的无缓冲的带来危害之后,我们开始真正的精简代码:

在这里插入图片描述

相比原始代码,这个代码更加简洁:

  1. 所有的代码不需要单独先声明,可以用 := 来声明
  2. 不需要用time.NewTimer,用time.After来替代
  3. 函数的返回值应该是(bool, error),这样可以知晓是超时,还是其他错误
  4. chan 的内容变成了error

这个代码其实还不是最精简的代码,回到一开始的代码用法:

在这里插入图片描述

简单明了,少了一个goroutine,代码更加简洁,也更加易读

总结

bug数量和代码数量成正比关系,这是一直强调的观点,在任何时候,少写代码能避免很多问题。

当然对自己使用的语言,特性一定要深入去学习了了解,不然到处埋坑,大部分时间都在修复bug,得不偿失

最后感谢群里提出改进意见的同学,让我学到了很多,也希望大家多多交流,共同进步

我们构建了一个100行代码项目的实战群,大家可以扫码加入,一起学习编程

也可以访问入职啦学习更多的编程实战

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值