案例背景

问题分析

真正的问题

解决方案

开始精简代码

总结

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

小葵

2024-11-22

🏷

go

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

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

案例背景

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

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

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

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

问题分析

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

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

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

  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行代码项目的实战群,大家可以扫码加入,一起学习编程

入群学习

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

入职啦

心仪的工作马上入职啦

友情链接:

Copyright© 2024 杭州园中葵科技有限公司 版权所有