這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go語言作為一個現代化的程式設計語言以及支援垃圾記憶體的自動回收特性(GC).
我們現在關注的是非記憶體資源的自動回收技術.
局部資源的管理
在討論Go語言解決方案之前, 我們先看看C++是怎麼管理資源的.
C++中可以可以自動執行的代碼主要是建構函式和解構函式.
因此, 很多資源的管理技術都是基於建構函式和解構函式實現.
比較常見的是C++的RAII(Resource Acquisition Is Initialization)技術,
即初始化中擷取資源. 比如在多線程編程中用到的MutexLocker:
struct MutexLock { Mutex *const mu_; MutexLock(Mutex *mu): mu_(mu) { mu_->Lock(); } ~MutexLock() { mu_->Unlock(); }};
這樣在使用Mutex的時候就不會忘記解鎖的操作了:
void* safeRead(Mutex *mu) { MutexLock locker(mu); if(...) { return NULL; } return read();}
其實RAII中最重要的是退出locker範圍是自動執行對象的解構函式,
這裡也就是mu_->Unlock();語句.
C++的建構函式其實是次要的. 關于禁用C++建構函式的討論可以參考我的
另一個文章: C++去掉建構函式會怎麼樣?
因為建構函式經常是通過顯示定義變數而隱式調用的, 因此用普通的全域函數也
可以實現建構函式的功能(唯一的約束是值容器).
其實C語言的fopen就是一個FILE對象的建構函式.
而作為C語言簡約哲學繼承者的Go語言同樣也沒有對建構函式做特殊處理.
在Go語言中建構函式這是約定以New開頭的普通函數, 比如NewBuffer.
Go語言/UNIX之父Ken Thompson發明了defer語句, 完美地
解決了解構函式的問題(defer還有很多其他特性).
因此, 在釋放局部資源時, 可以用defer管理. 因為C++的RAII的構造
函數和解構函式耦合過於緊密, 對於資源申請失敗的問題就比較麻煩.
但是Go語言的defer則靈活很多.
比如, Go語言版本基於defer的Mutex用法
func safeRead(Mutex *mu) []byte { mu.Lock() defer mu.Unlock() return read();}
對於可能申請失敗的資源也很好處理:
func loadFile(name string) ([]byte, error) { f, err := os.Open(name) if err != nil { return nil, err } defer f.Close() return load(f)}
使用defer語句, 可以方便地組合函數/閉包和資來源物件.
即使panic時, defer也能保證資源的正確釋放.
非局部資源的管理
我們之前看到的都是在局部使用和釋放資源.
如果資源的生命週期很長, 而且可能被多個模組共用和隨意傳遞的話,
defer語句就不好處理了.
解決的思路和C++的RAII的方式類似: 我們需要一個能夠自己定義的類似
解構函式的技術.
但是因為Go語言有GC特性, 因此沒有解構函式的概念. 不過runtime包的
func SetFinalizer(x, f interface{})函數可以提供類似的機制.
比如, 我們可以封裝一個檔案對象, 在沒有人使用的時候能夠自動關閉:
type MyFile struct { f *os.File}func NewFile(name string) (*MyFile, error) { f, err := os.Open(name) if err != nil { return nil, err } runtime.SetFinalizer(f, f.Close) return &MyFile{f:f}, nil}func (f *MyFile) Close() { f.f.Close()}
在使用runtime.SetFinalizer時, 需要注意的地方是盡量要用指標訪問
內部資源. 這樣的話, 即使*MyFile對象忘記釋放, 或者是被別的對象無意中覆蓋,
也可以保證內部的檔案資源可以正確釋放.
總結
Go語言是短小精悍的語言, 它的設計哲學來自UNIX和C語言的KISS原則.
但是Go語言的文法規範雖然很少(50+頁), 但是卻提供了無限可能的組合方式.
Go語言之父Rob Pike有篇文章叫 少是指數級的多. 但是為什麼少就是多呢?
參考下數學公理就明白了: 數學的基礎規則是很簡單的, 但是組合方式卻是無窮的.
Go語言的思路也是提供雖然少但卻是正交的基礎特性, 通過不同特性的無窮的
組合方式來應對各種問題(一個反例就是C++的建構函式和解構函式).
這裡我們主要是基於Go語言的defer和runtime.SetFinalizer兩個基礎特性,
來解決資源的自動回收問題.