4 回答

TA貢獻1883條經驗 獲得超3個贊
確實,垃圾收集時文件會關閉,但是...正如Alexander Morozov的“?Go 中終結器之謎”中提到的 - LK4D4math?:
在 Go 中,我們同時擁有 GC 和專業用戶:)
因此,在我看來,顯式調用Close
總是比魔術終結器更好。
亞歷山大補充道:
終結器的問題在于您無法控制它們,而且更重要的是,您并不期望它們。
看這段代碼:
func getFd(path string) (int, error) {
? ? f, err := os.Open(path)
? ? if err != nil {
? ? ? ? return -1, err
? ? }
? ? return f.Fd(), nil
}
當你為 Linux 編寫一些東西時,從路徑獲取文件描述符是非常常見的操作。
但該代碼是不可靠的,因為當您從 , 返回時getFd()
,f
會丟失其最后一個引用,因此您的文件注定遲早會被關閉(當下一個 GC 周期到來時)。在這里,問題不在于文件將被關閉,而在于它沒有記錄并且根本不是預期的。
有人提議擴展終結器并檢測泄漏(例如文件描述符泄漏)
但是……拉斯·考克斯令人信服地推翻了這一點:
任何對此主題感興趣的人都應該閱讀 Hans Boehm 的論文“析構函數、終結器和同步”。
它極大地影響了我們盡可能限制 Go 中終結器范圍的決定。
它們是允許與關聯堆內存同時回收非(堆內存)資源的必要之惡,但它們本質上比大多數人最初認為的更有限。我們不會擴展終結器的范圍,無論是在實現中還是在標準庫中,或者在 x 存儲庫中,我們也不會鼓勵其他人擴展該范圍。
如果您想跟蹤手動管理的對象,最好使用
runtime/pprof.NewProfile
.例如,在 Google 的源代碼樹中,我們有一個 Google 范圍內的“文件”抽象,并且 Go 包裝器包聲明了一個全局的:
var?profiles?=?pprof.NewProfile("file")
創建新文件的函數末尾顯示:
profiles.Add(f,?2) return?f
然后
f.Close
_
profiles.Remove(f)
然后我們可以獲得所有正在使用的文件的配置文件,無論是“泄露”還是其他方式,來自
/debug/pprof/file
或來自pprof.Lookup("file").WriteTo(w, 0)
.
該配置文件包括堆棧跟蹤。

TA貢獻1884條經驗 獲得超4個贊
Go 應用程序應該顯式釋放資源。當變量超出范圍時,沒有語言功能會隱式釋放資源。該語言確實提供了延遲功能,以便更輕松地編寫顯式代碼。
正如 bserdar 和 VonC 所指出的,垃圾收集器有一個用于釋放外部資源的鉤子。該鉤子由 os.File 類型使用。應用程序不應依賴此掛鉤,因為在收集對象時未指定它(如果有的話)。

TA貢獻1829條經驗 獲得超13個贊
當 os.File 被垃圾收集時,文件將自動關閉。這似乎是通過 SetFinalizer 調用完成的,因此文件最終將被關閉,而不是在它變得無法訪問后立即關閉。

TA貢獻1812條經驗 獲得超5個贊
在您的簡單示例中,defer
不需要該語句。該文件在退出函數作用域時關閉。
但是,如果您返回該變量f
或將其保存在全局變量中,則其引用計數器會增加,因此不會被刪除。
下面是一個示例,證明終結器以與初始化相反的順序立即被調用(由于引用計數器達到零,因此立即被垃圾收集):
package main
import (
? ? ? ? "fmt"
? ? ? ? "runtime"
? ? ? ? "time"
)
type Foo struct {
? ? ? ? name? ? string
? ? ? ? num? ? ?int
}
func? finalizer(f *Foo) {
? ? ? ? fmt.Println("a finalizer has run for ", f.name, f.num)
}
var counter int
func MakeFoo(name string) (a_foo *Foo) {
? ? ? ? a_foo = &Foo{name, counter}
? ? ? ? counter++
? ? ? ? runtime.SetFinalizer(a_foo, finalizer)
? ? ? ? return
}
func Bar() {
? ? ? ? f1 := MakeFoo("one")
? ? ? ? f2 := MakeFoo("two")
? ? ? ? fmt.Println("f1 is: ", f1.name)
? ? ? ? fmt.Println("f2 is: ", f2.name)
}
func main() {
? ? ? ? for i := 0; i < 3; i++ {
? ? ? ? ? ? ? ? Bar()
? ? ? ? ? ? ? ? time.Sleep(time.Second)
? ? ? ? ? ? ? ? runtime.GC()
? ? ? ? }
? ? ? ? fmt.Println("done.")
}
事實上,您無法真正控制終結器是否被調用。但是,如果您確保不將局部變量復制到任何地方,那么它會立即發生。
實際上,一個有趣的事實是,如果您使用 defer,那么您將創建對該對象的引用(您需要有一個引用才能Close()在退出函數時調用該對象)。
這是非常重要的一點,因為如果有循環,實際上會使情況變得更糟:
? ? ...
? ? for i:=0; i<10; i++ {
? ? ? ? f := os.Open(...)
? ? ? ? defer f.Close()
? ? ? ? ...
? ? } // without the defer, the file is closed here, as expected
} // <- defer is called here, so you will open 10 files
? // and keep all 10 open until this line
所以你必須非常小心defer。有些情況下它不起作用。
- 4 回答
- 0 關注
- 228 瀏覽
添加回答
舉報