如何在循環中使用Defer 來釋放資源:最佳方法
在循環中涉及資料庫查詢的上下文中,問題如下:延遲語句的最佳放置以確保正確的資源釋放。考慮以下循環:
for rows.Next() { fields, err := db.Query(.....) if err != nil { // ... } defer fields.Close() // do something with `fields` }
出現延遲放置的兩個選項:
循環體內延遲:
for rows.Next() { fields, err := db.Query(.....) if err != nil { // ... } // do something with `fields` } defer fields.Close()
循環後延後body:
for rows.Next() { fields, err := db.Query(.....) if err != nil { // ... } // do something with `fields` defer fields.Close() }
了解延遲執行
要選擇最佳方法,我們必須先了解延遲的行為。延遲函數不僅會延遲到周圍函數返回,而且即使函數由於異常(例如恐慌)而突然終止也會執行。這是即使在特殊情況下也能確保資源釋放的重要機制。
延遲放置的潛在問題
在循環內放置延遲可能會阻礙循環終止時的資源釋放。如果迴圈因迴圈內處理的錯誤而提前退出,則 deferred fields.Close() 呼叫將不會執行。
相反,將 defer 放在循環體之後可以保證無論如何釋放資源循環退出。但是,這種方法會延遲資源清理,直到整個循環完成之後,這可能不適合所有場景。
最佳解決方案:匿名或命名函數包裝器
至為了解決這兩個問題,建議的解決方案是將資源分配和釋放封裝在匿名或命名函數中。透過這樣做,可以在函數內使用 defer 以確保函數返回時釋放資源。
例如:
// Anonymous function wrapper for rows.Next() { func() { fields, err := db.Query(...) if err != nil { // Handle error and return return } defer fields.Close() // do something with `fields` }() } // Named function wrapper func foo(rs *db.Rows) { fields, err := db.Query(...) if err != nil { // Handle error and return return } defer fields.Close() // do something with `fields` } for rows.Next() { foo(rs) }
這種方法允許在資源不再可用時立即釋放即使在例外情況下也是需要的。此外,如果目標是在出現第一個錯誤時終止循環,則可以從包裝函數傳回錯誤並進行對應處理:
func foo(rs *db.Rows) error { fields, err := db.Query(...) if err != nil { return fmt.Errorf("db.Query error: %w", err) } defer fields.Close() // do something with `fields` return nil } for rows.Next() { if err := foo(rs); err != nil { // Handle error and return return } }
使用Rows.Close() 進行錯誤處理
需要注意的是Rows.Close() 回傳錯誤。要處理此錯誤,可以使用延遲呼叫 Rows.Close() 的匿名函數:
func foo(rs *db.Rows) (err error) { fields, err := db.Query(...) if err != nil { return fmt.Errorf("db.Query error: %w", err) } defer func() { if err = fields.Close(); err != nil { err = fmt.Errorf("Rows.Close() error: %w", err) } }() // do something with `fields` return nil }
以上是Go 中應該在迴圈中的哪裡放置 defer 語句才能正確釋放資源?的詳細內容。更多資訊請關注PHP中文網其他相關文章!