Golang 文件描述符泄露(too many open files)

sin 2022-05-25 PM 1518℃ 0条

在使用Golang 调用net/http客户端的时候,如果概念不清楚,经常会发生文件描述符泄露的情况。

垃圾回收和资源回收

首先要搞清楚的概念是

  • 垃圾回收
  • 资源回收

垃圾回收我们指的是在变量不使用的情况下,Go语言的垃圾回收机制可以帮助我们自动回收内存。与之对应的概念应该是手动回收垃圾,比如我们c语言中malloc了一块内存,需要手动free释放。

资源回收我们指不使用的资源需要关闭、回收、释放。这个Go并没有自动的机制。比如我们打开的一个文件,占用了一个文件描述符;如果不关闭文件,则一直占用这个文件描述符,导致泄露。

responsibility

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {

    ticker := time.NewTicker(1 * time.Second)

    client := &http.Client{}

    for {

        select {
        case <-ticker.C:

            req, err := http.NewRequest("GET", "https://www.baidu.com", nil)
            if err != nil {
                panic(err)
            }

            res, err := client.Do(req)
            if err != nil {
                panic(err)
            }
            fmt.Println(res.Status)
            // res.Body.Close()  // 正确方法应该把这一行注释去掉
            // defer res.Body.Close() // 完全错误的写法, 请看下面 **defer 延迟调用** 的解释
        }
    }
}

当我们调用 res.Body.Close() 随着时间增加,打开的文件逐渐增多

20220525224415.jpg

defer 延迟调用

在代码过程中,我还犯了另一个错误,忽略了defer的调用时机。我原以为,defe是就好比c++智能指针那种技术,是把函数注册到变量的析构函数中,然后在变量失去作用域之后,被自动调用。

其实 defer 向函数注册退出调用,即主函数退出时,defer后的函数才被调用。

而上面的代码,由于使用了死循环,函数没有机会退出,所以导致 关闭文件的函数永远不会被调用。

我们可以重新定义一个函数,来解决这个问题。

func call_req(client *http.Client, request *http.Request) {
    res, err := client.Do(request)
    defer res.Body.Close()
    if err != nil {
        panic(err)
    }
    fmt.Println(res.Status)
}

func main() {

    ticker := time.NewTicker(1 * time.Second)

    client := &http.Client{}

    for {

        select {
        case <-ticker.C:

            req, err := http.NewRequest("GET", "https://www.baidu.com", nil)
            if err != nil {
                panic(err)
            }

            call_req(client, req)
        }
    }
}

20220525224137.jpg

标签: none

非特殊说明,本博所有文章均为博主原创。

评论啦~