深入瞭解Go的interface{}底層原理實現

1. interface{}初探

Go是強類型語言,各個實例變量的類型信息正是存放在interface{}中的,Go中的反射也與其底層結構有關。

ifaceeface 都是 Go 中描述interface{}的底層結構體,區別在於 iface 描述的接口包含方法,而 eface 則是不包含任何方法的空接口:interface{}

接下來,我們將詳細剖析ifaceeface的底層數據結構。

2. eface

eface 比較簡單,隻維護瞭 _type 字段,表示空接口所承載的具體的實體類型,以及data 描述瞭具體的值。

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

data字段是ifaceeface都有的結構,這個是一個內存指針,指向interface{}實例對象信息的存儲地址,在這裡,我們可以獲取對象的具體屬性的數值信息。

而interface{}的類型信息是存放在_type結構體中的,如下所示,在eface中,直接存放瞭_type的指針,iface中多瞭一層封裝,本節我們主要針對eface做梳理,所以介紹_type結構體。

type _type struct {
    // 類型大小
    size       uintptr
    ptrdata    uintptr
    // 類型的 hash 值
    hash       uint32
    // 類型的 flag,和反射相關
    tflag      tflag
    // 內存對齊相關
    align      uint8
    fieldalign uint8
    // 類型的編號,有bool, slice, struct 等等等等
    kind       uint8
    alg        *typeAlg
    // gc 相關
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

我們可以看到size,ptrdata等表示interface{}對象的類型信息,hash是其對應的哈希值,用於map等的哈希算法,tflag與反射相關,而alignfieldalign是用來內存對齊的,這與Go底層的內存管理機制有關,Go的內存管理機制類似於Linux中的夥伴系統,是以固定大小的內存塊進行內存分配的,與這個大小進行對齊消除外碎片,提高內存利用率。另外還有一些和gc相關的參數,大傢有一個初步的理解與認識就可以瞭,如果想深入掌握可以專門學習和查看源碼。

3. iface

eface不同,iface結構體中要同時儲存方法信息,其數據結構如下圖所示。正如前面所說的,itab結構體封裝瞭_type結構體,同樣利用_type儲存類型信息,另外,其還有一些其他的屬性。hash是對_type結構體中hash的拷貝,提高類型斷言的效率。badinhash都是標記位,提高gc以及其他活動的效率。fun指向方法信息的具體地址。

另外,interfacetype,他描述的是接口靜態類型信息。

fun 字段放置和接口方法對應的具體數據類型的方法地址,實現接口調用方法的動態分派,一般在每次給接口賦值發生轉換時會更新此表,或者直接拿緩存的 itab。這裡隻會列出實體類型和接口相關的方法,實體類型的其他方法並不會出現在這裡。如果你學過 C++ 的話,這裡可以類比虛函數的概念,至於靜態函數,並不存放在這裡。

C++ 和 Go 在定義接口方式上的不同,也導致瞭底層實現上的不同。C++ 通過虛函數表來實現基類調用派生類的函數;而 Go 通過 itab 中的 fun 字段來實現接口變量調用實體類型的函數。C++ 中的虛函數表是在編譯期生成的;而 Go 的 itab 中的 fun 字段是在運行期間動態生成的。原因在於,Go 中實體類型可能會無意中實現 N 多接口,很多接口並不是本來需要的,所以不能為類型實現的所有接口都生成一個 itab, 這也是“非侵入式”帶來的影響;這在 C++ 中是不存在的,因為派生需要顯示聲明它繼承自哪個基類。

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter  *interfacetype
    _type  *_type
    link   *itab
    hash   uint32 // copy of _type.hash. Used for type switches.
    bad    bool   // type does not implement interface
    inhash bool   // has this itab been added to hash?
    unused [2]byte
    fun    [1]uintptr // variable sized
}

type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}

綜合上面的分析,我們可以梳理出,iface對應的幾個重要數據結構的關系如下圖所示。

4. 接口轉化

通過前面提到的 iface 的源碼可以看到,實際上它包含接口的類型 interfacetype 和 實體類型的類型 _type,這兩者都是 iface 的字段 itab 的成員。也就是說生成一個 itab 同時需要接口的類型和實體的類型。

->itable

當判定一種類型是否滿足某個接口時,Go 使用類型的方法集和接口所需要的方法集進行匹配,如果類型的方法集完全包含接口的方法集,則可認為該類型實現瞭該接口。

例如某類型有 m 個方法,某接口有 n 個方法,則很容易知道這種判定的時間復雜度為 O(mn),Go 會對方法集的函數按照函數名的字典序進行排序,所以實際的時間復雜度為 O(m+n)

Go的接口實現是非侵入式的,而是鴨子模式:如果某個東西長得像鴨子,像鴨子一樣遊泳,像鴨子一樣嘎嘎叫,那它就可以被看成是一隻鴨子。

因此,隻要我們實現瞭接口對應的方法,也就實現瞭對應的接口,不需要單獨申明。

到此這篇關於深入瞭解Go的interface{}底層原理實現的文章就介紹到這瞭,更多相關Go interface{}底層原理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: