亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如果沒有顯式關閉,Go 會自動關閉資源嗎?

如果沒有顯式關閉,Go 會自動關閉資源嗎?

Go
泛舟湖上清波郎朗 2023-07-31 10:37:40
以下內容Open后跟 deferredClose在 Go 中是慣用的:func example() {    f, err := os.Open(path)    if err != nil {        return    }    defer f.Close()}如果我沒有會怎樣defer f.Close()?當我調用此函數并f超出范圍時,它會自動關閉文件還是我有僵尸文件句柄?如果它自動關閉,具體什么時候關閉?
查看完整描述

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).
該配置文件包括堆棧跟蹤。


查看完整回答
反對 回復 2023-07-31
?
慕村9548890

TA貢獻1884條經驗 獲得超4個贊

Go 應用程序應該顯式釋放資源。當變量超出范圍時,沒有語言功能會隱式釋放資源。該語言確實提供了延遲功能,以便更輕松地編寫顯式代碼。

正如 bserdar 和 VonC 所指出的,垃圾收集器有一個用于釋放外部資源的鉤子。該鉤子由 os.File 類型使用。應用程序不應依賴此掛鉤,因為在收集對象時未指定它(如果有的話)。


查看完整回答
反對 回復 2023-07-31
?
烙印99

TA貢獻1829條經驗 獲得超13個贊

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



查看完整回答
反對 回復 2023-07-31
?
慕雪6442864

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。有些情況下它不起作用。


查看完整回答
反對 回復 2023-07-31
  • 4 回答
  • 0 關注
  • 228 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號