Go语言中常见100问题-#35 defer使用陷阱与解决方法

前言

defer语句会延迟语句在函数返回时执行.例如,如果资源最后必须要关闭,可以使用defer避免在每个return返回的地方调用close操作。但是,在for循环中使用defer会存在问题,很多Gopher没有意识到这一点。

案例引入

下面通过一个问题代码来分析说明。readFiles函数接收一组文件,然后通过循环遍历每个文件,读取文件内容,最后关闭文件。

golang 复制代码
func readFiles(ch <-chan string) error {
    for path := range ch {
        file, err := os.Open(path)
        if err != nil {
            return err
        }

        defer file.Close()
        // Do something with file
    }
    return nil
}

上述代码是有问题的,我们在循环内部调用defer关闭文件句柄,但是defer语句会延迟到函数返回时执行,也就是说,for range循环不结束,所有打开的文件句柄都不会释放,这导致资源泄露。

解决方法

如何修复上述问题呢?一种处理方法是不使用defer语句,主动调用close操作,但这会失去使用defer语句(Go语言为我们提供的很方便的操作)机会。如果还是希望使用defer语句,有没有解决方法呢?是有的,我们可以将循环内部的逻辑封装到一个函数中,这样就可以继续使用defer语句了,实现代码如下。

下面的readFile函数打开文件,调用defer关闭句柄,最后执行一些文件相关的操作。现在在循环中调用的是readFile函数操作,每次循环打开的文件都会很快关闭。

golang 复制代码
func readFiles(ch <-chan string) error {
    for path := range ch {
        if err := readFile(path); err != nil {
            return err
        }
    }
    return nil
}

func readFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }

    defer file.Close()
    // Do something with file
    return nil
}

此外,还可以将上面的readFile函数放入闭包中,采用匿名函数执行,实现代码如下。本质上来说,这种方法与上面的是一样的,相比起来前一种方法代码看起来更清晰一些,可以单独为其编写单元测试代码。

golang 复制代码
func readFiles(ch <-chan string) error {
    for path := range ch {
        err := func() error {
        // ...
        defer file.Close()
        // ...
        }()
        if err != nil {
            return err
        }
    }
    return nil
}

思考总结

使用defer时,我们需要牢记defer语句会延迟到函数返回时执行。因此,在循环中的defer调用不会在每轮结束时执行,这可能导致资源泄露。一种自然而然想到的方法是将循环内部的逻辑放到一个函数中,在循环内部直接调用这个函数,这种方法的缺点是函数调用会增加开销,如果性能至关重要,要留意这种开销,可以不使用defer,手动管理资源关闭。

相关推荐
期待のcode6 小时前
MyBatisX插件
java·数据库·后端·mybatis·springboot
AI浩8 小时前
【Labelme数据操作】LabelMe标注批量复制工具 - 完整教程
运维·服务器·前端
涔溪8 小时前
CSS 网格布局(Grid Layout)核心概念、基础语法、常用属性、实战示例和进阶技巧全面讲解
前端·css
2401_878454538 小时前
浏览器工作原理
前端·javascript
未来魔导8 小时前
go语言中json操作总结
数据分析·go·json
西陵8 小时前
为什么说 AI 赋能前端开发,已经不是选择题,而是必然趋势?
前端·架构·ai编程
华仔啊9 小时前
这 10 个 MySQL 高级用法,让你的代码又快又好看
后端·mysql
码事漫谈9 小时前
国产时序数据库崛起:金仓凭什么在复杂场景中碾压InfluxDB
后端
上进小菜猪9 小时前
当时序数据不再“只是时间”:金仓数据库如何在复杂场景中拉开与 InfluxDB 的差距
后端
by__csdn10 小时前
Vue3 setup()函数终极攻略:从入门到精通
开发语言·前端·javascript·vue.js·性能优化·typescript·ecmascript