由一個問題引發對文件描述符的研究

故事的起因

一次一個同事給我發了一段簡單的代碼,問我這段代碼有什麼問題?

package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(f.Name(), "opened successfully")
}

看到這段代碼後不加思索的回答,文件沒有close,他說錯,可能當時我們沒在一個頻道上,“err處理沒有return”。

又仔細的看了下代碼,發現err的處理代碼塊後使用了f.Name(),這個是存在問題的,因爲當open發生錯誤時,返回的文件句柄則爲nil,下文直接使用f.Name()。這種錯誤對於初學者經常會犯,改進的方式也很多,只要保證運行f.Name()的得到的f不爲nil即可。
可以在發生錯誤時,可以return或者os.Exit(-1) 也或下文的f.Name()放到else邏輯塊中。

具體的處理方式要根據對報錯的容忍度來處理

故事的發展

  • 猜想

剛又提到,程序未對打開的文件做close,當然運行也沒問題。既然沒問題,也就沒有close的必要。但是在open後加defer close已經成爲go語言教課書級的示例。
猜想,這裏的open底層是一個I/O操作,在linux下所有的I/O操作都會轉化爲對文件的操作。如果程序對文件open後,沒有關閉,則會一直佔有資源,打開的數量越來越多,最終一定會因達到上限而導致程序出現問題。

  • 猜想調查
    通過谷歌找到lsof這一命令可以查看打開的文件描述符的上限。

    通過改命令發現我電腦上可以支持程序最大打開的文件描述符是4864個
  • 驗證

修改下代碼,看下當程序打開4865次會發生什麼情況?

package main

import (
    "fmt"
    "os"
)

func main() {
    for i := 1; i <= 4865; i++ {
        f, err := os.Open("./test.txt")
        if err != nil {
            fmt.Println(err)
        }
        fmt.Println(f.Name(), "opened successfully", i)
    }
    fmt.Scanln()
}

執行結果

發生了猜想中的問題,剛查看最大文件描述符是4864,這裏只打開了4861個,爲什麼少了三個?

  • 再次猜想

這裏少了三個,那麼這三個應該是被系統佔用了,這裏存在兩種可能:

    1. 被其它程序佔用
    2. 被該程序佔用
    • 再次驗證

    先來確認第一點,被其它程序佔用
    怎麼驗證呢?可以同樣的程序,一個循環數設置3000,一個設置2000,如果結論成立的話,那麼後運行的一個一定會出錯。

    程序並沒有向想象中的那樣出錯

    那麼就是該程序默認佔用了三個
    通過lsof查下進程打開的描述符情況

    發現程序會默認打開三個系統文件描述符
    也就是標準輸入,標準輸出,錯誤輸出

    這樣的解釋就可以自說其圓了,真的是這樣麼?

    • 理論支撐

      以下是維基百科對文件描述符的敘述

    對文件的描述符的探索,可以畫上一個句號了。

    遺留問題


    在產看進程關聯的文件時,發現有多出以上四個,這些有什麼?這個問題作爲一個遺留問題拋在這裏,等待有心去探索

    總結

    通過以上的試驗和驗證,在程序打開文件後,記得close
    完善後的最終處理代碼

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main() {
        for i := 1; i <= 50000; i++ {
            f, err := os.Open("./test.txt")
            if err != nil {
                fmt.Println(err)
                return
            }
            fmt.Println(f.Name(), "opened successfully", i)
            f.Close()
        }
        fmt.Scanln()
    }

    喜歡請關注微信公衆號“雲端漫記" 持續爲你更新
    圖片描述

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章