一次一個同事給我發了一段簡單的代碼,問我這段代碼有什麼問題?linux
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邏輯塊中。spa
具體的處理方式要根據對報錯的容忍度來處理3d
剛又提到,程序未對打開的文件作close,固然運行也沒問題。既然沒問題,也就沒有close的必要。可是在open後加defer close已經成爲go語言教課書級的示例。
猜測,這裏的open底層是一個I/O操做,在linux下全部的I/O操做都會轉化爲對文件的操做。若是程序對文件open後,沒有關閉,則會一直佔有資源,打開的數量愈來愈多,最終必定會因達到上限而致使程序出現問題。code
修改下代碼,看下當程序打開4865次會發生什麼狀況?blog
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個,爲何少了三個?圖片
這裏少了三個,那麼這三個應該是被系統佔用了,這裏存在兩種可能:資源
先來確認第一點,被其它程序佔用
怎麼驗證呢?能夠一樣的程序,一個循環數設置3000,一個設置2000,若是結論成立的話,那麼後運行的一個必定會出錯。
程序並無向想象中的那樣出錯rem
那麼就是該程序默認佔用了三個
經過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() }
喜歡請關注微信公衆號「雲端漫記" 持續爲你更新