2017-08-10 146 views
1

我需要SQL查詢數據庫,在循環:循環中的`defer` - 什麼會更好?

for rows.Next() { 

    fields, err := db.Query(.....) 
    if err != nil { 
     // ... 
    } 
    defer fields.Close() 

    // do something with `fields` 

} 

什麼會更好:把所有原樣或移動defer循環後:

for rows.Next() { 

    fields, err := db.Query(.....) 
    if err != nil { 
     // ... 
    } 

    // do something with `fields` 
} 

defer fields.Close() 

或者其他什麼東西?

+4

不要在一個循環內推遲。 – Volker

+0

[Golang推遲澄清]的相關/可能的重複(https://stackoverflow.com/questions/28893586/golang-defer-clarification/28894103#28894103)。 – icza

回答

6

defer整點是它不會執行,直到函數返回,所以適當的地方把它放在你想關閉的資源後立即打開。但是,由於您在循環內部創建了資源,因此您不應該使用延遲 - 否則,您不會關閉在循環內部創建的任何資源,直到該函數退出,所以它們會一直堆積到然後。相反,你應該在每次循環迭代,年底關閉它們沒有defer

for rows.Next() { 

    fields, err := db.Query(.....) 
    if err != nil { 
     // ... 
    } 

    // do something with `fields` 

    fields.Close() 
} 
+0

除此之外,在這種情況下,defer並不會像OP期望的那樣工作,因爲它只會關閉循環中最後一個'fields'(它需要一個閉包才能正常工作)。順便說一下,使用'defer'將匿名'func'中的循環內體包裹起來可能是一個很好的解決方案。 – Carpetsmoker

+0

正確 - 但即使封閉工作正確*,它仍然無法正常工作。 – Adrian

5

遞延函數的執行不僅被延遲,推遲到此刻周圍的函數返回時,它也甚至被處決如果封閉函數突然終止,例如恐慌。 Spec: Defer statements:

A「推遲」語句調用它的執行被推遲到此刻周圍的函數返回的功能,要麼是因爲周圍的功能執行的return statement,達到其function body或者是因爲相應的goroutine是結束​​

當你創建一個值或提供手段妥善關閉/處理它的資源,你應該總是使用一個defer語句,以確保它是否被釋放,即使你的其他代碼恐慌,以防止泄漏內存或其他系統資源。

這是真的,如果你在一個循環中分配資源,你不應該簡單地使用defer,作爲然後釋放資源不會發生早,因爲它可以而且應該(在每次迭代結束),之後for聲明(僅在所有迭代之後)。

你應該做的是,如果你有一個分配這些資源的代碼片段,將它包裝在一個函數中 - 無論是匿名還是命名函數 - ,並且在那個函數中你可以使用defer,資源將被釋放爲一旦不再需要它們,重要的是,即使代碼中存在可能引起恐慌的錯誤。

例子:

for rows.Next() { 
    func() { 
     fields, err := db.Query(...) 
     if err != nil { 
      // Handle error and return 
      return 
     } 
     defer fields.Close() 

     // do something with `fields` 
    }() 
} 

如果放在一個名爲功能:

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) 
}