【我的架構師之路】- golang源碼分析之interface的底層實現

【轉載請標明出處】:https://blog.csdn.net/qq_25870633/article/details/83448222

今天我們來說一說 golang中的interface的講解。golang的interface 類似java的Object,也類似 scala中的Any,類似於C++中的void*,但是又不一樣。

interface 是否包含有 method,底層實現上用兩種 struct 來表示:iface eface

eface表示不含 method 的 interface 結構,或者叫 empty interface。對於 Golang 中的大部分數據類型都可以抽象出來 _type 結構,同時針對不同的類型還會有一些其他信息。

iface 表示 non-empty interface 的底層實現。相比於 empty interface,non-empty 要包含一些 methodmethod 的具體實現存放在 itab.fun 變量裏

廢話不多說,我們直接上代碼先:(被定義在  src/runtime/runtime2.go 中)

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

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

上述就是兩種 interface 的定義。然後我們再看 iface中的 itab 結構:(被定義在 src/runtime/runtime2.go 中)

我這裏有三個版本的 itab 定義,分別是:

【1.8.1】:

// 1.8.1 版本定義
type itab struct {
	inter *interfacetype	//  接口的類型
	_type *_type			//	實際對象類型
	hash  uint32 			//  賦值 類型的Hash 用於做類型轉換用
	_     [4]byte			
	fun   [1]uintptr 		// 可變大小。 fun [0] == 0表示_type不實現inter.
}

【1.9.7】:

// 1.9.7 版本
type itab struct {
	inter  *interfacetype	//  接口的類型
	_type  *_type			//	實際對象類型
	link   *itab
	hash   uint32 			//	賦值 類型的Hash 用於做類型轉換用
	bad    bool   			//	type是否實現接口,標識位
	inhash bool   			// 	添加自身Hash
	unused [2]byte			// 	並沒有找到地方用 ...
	fun    [1]uintptr 		// 實際對象的func 隊列的首地址
}

【1.10.4】:

// 1.10.4 版本
// 在非 gc 內存中分配的編譯器已知的Itab 存儲佈局並需要與(../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs)同步
// 這裏官方的註釋錯了,應該是 ../cmd/compile/internal/gc/reflect.go:/^func.dumptabs
type itab struct {
	inter *interfacetype	//  接口的類型
	_type *_type			//	實際對象類型
	hash  uint32 			//  賦值 類型的Hash 用於做類型轉換用
	_     [4]byte
	fun   [1]uintptr 		// 可變大小。 fun [0] == 0表示_type不實現inter.
}

我們可以看到 三個版本中都有主要的三個部分,【一】代表接口自身類型的 interfacetype;【二】表示 實際對象類型 的 _type;【三】實際對象的func 隊列的首地址 fun    [1]uintptr;緊接着我們看下 interfacetype

// 1.10.4 版本 // 1.9.7版本  // 1.8.1
type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

我們可以看出來  interfacetype  裏頭其實是存放了 _type 的。而在itab中存放了 _type的指針。那麼,下面我們先來看一看 _type 的定義:

// 需要和這些東東同步 ../cmd/link/internal/ld/decodesym.go:/^func.commonsize,
// ../cmd/compile/internal/gc/reflect.go:/^func.dcommontype and
// ../reflect/type.go:/^type.rtype.
type _type struct {
	size       uintptr	// 	表示類型的寬度/長度
	ptrdata    uintptr 	// 	包含所有指針的內存前綴的大小
	hash       uint32	//	Hash類型; 避免在哈希表中計算
	tflag      tflag	// 	額外類型信息標誌
	align      uint8	//	該類型的變量對齊方式
	fieldalign uint8	//	該類型的結構字段對齊方式
	kind       uint8	//	C的枚舉 (幹嘛用的?)
	alg        *typeAlg	//	算法表
	//	gcdata存儲垃圾收集器的GC類型數據
	//	如果 KindGCProg 位(用不同的bit來標識動作含義)被設置在kind字段中,則gcdata是GC程序。
	//	否則它是一個ptrmask 位圖。 有關詳細信息,請參閱 mbitmap.go。
	gcdata    *byte		// gc 的數據
	str       nameOff	// 字符串形式
	ptrToThis typeOff	// 指向此類型的指針的類型可以爲零
}



// typeAlg is 總是 在 reflect/type.go 中 copy或使用.
// 並保持他們同步.
type typeAlg struct {
	// 算出該類型的Hash
	// (ptr to object, seed) -> hash
	hash func(unsafe.Pointer, uintptr) uintptr
	// 比較該類型對象
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
}

type nameOff int32
type typeOff int32

好了,以上就是 iface 類型的定義,下面我們再來看看 eface類型的定義:

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

可以看出在 eface 中就更加簡單了,完全就是 定義了一個 _type 的指針,指向數據的指針 data,然後就沒了

那麼,下面我們可能會着重講一講 iface 了。

我們先來看一看,可能分別具備兩種接口的對象,分別賦值給兩種接口:

我們可以看到,變化分別在 第7 行的 var ei interface{} = a ;和第11行的 fmt.Println(ei, ii) 處,分別可以爲變量賦值給了兩種不同的接口。看上面的函數原型,可以看出中間過程編譯器將根據我們的轉換目標類型的 empty interface 還是 non-empty interface,來對原數據類型進行轉換(轉換成 <*_type, unsafe.Pointer> 或者 <*itab, unsafe.Pointer>)。這裏對於 struct 滿不滿足 interface 的類型要求(也就是 struct 是否實現了 interface 的所有 method),是由編譯器來檢測的。

現在我們回到 interfacetype  類型定義中,在 interfacetype 中包含了一些關於 interface 本身的信息,比如 package path,包含的 method。我們再來深入看看 method 切片的類型  imethod


//這裏的 method 只是一種函數聲明的抽象,比如  func Print() error
type imethod struct {
   name nameOff
   ityp typeOff
}

type nameOff int32
type typeOff int32

【注意】method 存的是func 的聲明抽象,而 itab 中的 func 字段纔是存儲 func 的真實切片。

_type :表示 concrete type (具體類型)。fun 表示的 interface 裏面的 method 的具體實現。比如 interface type 包含了 method A, B,則通過 fun 就可以找到這兩個 method 的具體實現。

【注意】以下是copy了 http://legendtkl.com/2017/07/01/golang-interface-implement/ 的:

這裏有個問題 fun 是長度爲 1 的 uintptr 數組,那麼怎麼表示多個 method 呢?看一下測試程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

type MyInterface interface {
    Print()
    Hello()
    World()
    AWK()
}

func Foo(me MyInterface) {
    me.Print()
    me.Hello()
    me.World()
    me.AWK()
}

type MyStruct struct {}

func (me MyStruct) Print() {}
func (me MyStruct) Hello() {}
func (me MyStruct) World() {}
func (me MyStruct) AWK() {}

func main() {
    var me MyStruct
    Foo(me)
}

看一下函數調用對應的彙編代碼:

以下是該博客原文的圖:

其中 0x18(SP) 對應的 itab 的值。fun 在 x86-64 機器上對應 itab 內的地址偏移爲 8+8+8+4+4 = 32 = 0x20,也就是 0x20(AX) 對應的 fun 的值,此時存放的 AWK 函數地址。然後 0x28(AX) = &Hello,0x30(AX) = &Print,0x38(AX) = &World。對的,每次函數是按字典序排序存放的

以下是我自己操作的圖:

 

我們再來看一下函數地址究竟是怎麼寫入的?首先 Golang 中的 uintptr 一般用來存放指針的值,這裏對應的就是函數指針的值(也就是函數的調用地址)。但是這裏的 fun 是一個長度爲 1 的 uintptr 數組。我們看一下 (src/runtime/ifce.go 文件中) runtime 包的 itabAdd 函數(注意了我這裏用 1.10.4來講解的,1.9.x及之前的版本叫做 additab )。

// itabAdd adds the given itab to the itab hash table.
// itabLock must be held.
func itabAdd(m *itab) {
	// Bugs can lead to calling this while mallocing is set,
	// typically because this is called while panicing.
	// Crash reliably, rather than only when we need to grow
	// the hash table.
	if getg().m.mallocing != 0 {
		throw("malloc deadlock")
	}

	t := itabTable
	if t.count >= 3*(t.size/4) { // 75% load factor
		// Grow hash table.
		// t2 = new(itabTableType) + some additional entries
		// We lie and tell malloc we want pointer-free memory because
		// all the pointed-to values are not in the heap.
		t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
		t2.size = t.size * 2

		// Copy over entries.
		// Note: while copying, other threads may look for an itab and
		// fail to find it. That's ok, they will then try to get the itab lock
		// and as a consequence wait until this copying is complete.
		iterate_itabs(t2.add)
		if t2.count != t.count {
			throw("mismatched count during itab table copy")
		}
		// Publish new hash table. Use an atomic write: see comment in getitab.
		atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
		// Adopt the new table as our own.
		t = itabTable
		// Note: the old table can be GC'ed here.
	}
	t.add(m)
}


// add adds the given itab to itab table t.
// itabLock must be held.
func (t *itabTableType) add(m *itab) {
	// See comment in find about the probe sequence.
	// Insert new itab in the first empty spot in the probe sequence.
	mask := t.size - 1
	h := itabHashFunc(m.inter, m._type) & mask
	for i := uintptr(1); ; i++ {
		p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
		m2 := *p
		if m2 == m {
			// A given itab may be used in more than one module
			// and thanks to the way global symbol resolution works, the
			// pointed-to itab may already have been inserted into the
			// global 'hash'.
			return
		}
		if m2 == nil {
			// Use atomic write here so if a reader sees m, it also
			// sees the correctly initialized fields of m.
			// NoWB is ok because m is not in heap memory.
			// *p = m
			atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
			t.count++
			return
		}
		h += i
		h &= mask
	}
}

func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
	// compiler has provided some good hash codes for us.
	return uintptr(inter.typ.hash ^ typ.hash)
}

然後在看 init 函數:

// init fills in the m.fun array with all the code pointers for
// the m.inter/m._type pair. If the type does not implement the interface,
// it sets m.fun[0] to 0 and returns the name of an interface function that is missing.
// It is ok to call this multiple times on the same m, even concurrently.
func (m *itab) init() string {
	inter := m.inter
	typ := m._type
	x := typ.uncommon()

	// both inter and typ have method sorted by name,
	// and interface names are unique,
	// so can iterate over both in lock step;
	// the loop is O(ni+nt) not O(ni*nt).
	ni := len(inter.mhdr)
	nt := int(x.mcount)
	xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
	j := 0
imethods:
	for k := 0; k < ni; k++ {
		i := &inter.mhdr[k]
		itype := inter.typ.typeOff(i.ityp)
		name := inter.typ.nameOff(i.name)
		iname := name.name()
		ipkg := name.pkgPath()
		if ipkg == "" {
			ipkg = inter.pkgpath.name()
		}
		for ; j < nt; j++ {
			t := &xmhdr[j]
			tname := typ.nameOff(t.name)
			if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
				pkgPath := tname.pkgPath()
				if pkgPath == "" {
					pkgPath = typ.nameOff(x.pkgpath).name()
				}
				if tname.isExported() || pkgPath == ipkg {
					if m != nil {
						ifn := typ.textOff(t.ifn)
						*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
					}
					continue imethods
				}
			}
		}
		// didn't find method
		m.fun[0] = 0
		return iname
	}
	m.hash = typ.hash
	return ""
}

在 init 函數中的for中有一句: *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn  ;意思是在 fun[0] 的地址後面依次寫入其他 method 對應的函數指針。而上面的 itabHashFunc 函數 則是:通過 runtime 包裏面有一個 hash 表,hash[hashitab(interface_type, concrete_type)] 可以取得 itab

【interface的類型斷言】

大家都知道在使用interface 強轉會實際的類型時,一般都會用到的額類型斷言:

 n, ok := v.(int)

那麼,interface 的類型斷言,底層是如何實現的呢?

主要是下面幾個 func 來實現的:

func assertI2I(inter *interfacetype, i iface) (r iface) {
	tab := i.tab
	if tab == nil {
		// explicit conversions require non-nil interface value.
		panic(&TypeAssertionError{"", "", inter.typ.string(), ""})
	}
	if tab.inter == inter {
		r.tab = tab
		r.data = i.data
		return
	}
	r.tab = getitab(inter, tab._type, false)
	r.data = i.data
	return
}

func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) {
	tab := i.tab
	if tab == nil {
		return
	}
	if tab.inter != inter {
		tab = getitab(inter, tab._type, true)
		if tab == nil {
			return
		}
	}
	r.tab = tab
	r.data = i.data
	b = true
	return
}

func assertE2I(inter *interfacetype, e eface) (r iface) {
	t := e._type
	if t == nil {
		// explicit conversions require non-nil interface value.
		panic(&TypeAssertionError{"", "", inter.typ.string(), ""})
	}
	r.tab = getitab(inter, t, false)
	r.data = e.data
	return
}

func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) {
	t := e._type
	if t == nil {
		return
	}
	tab := getitab(inter, t, true)
	if tab == nil {
		return
	}
	r.tab = tab
	r.data = e.data
	b = true
	return
}

//go:linkname reflect_ifaceE2I reflect.ifaceE2I
func reflect_ifaceE2I(inter *interfacetype, e eface, dst *iface) {
	*dst = assertE2I(inter, e)
}

好了,今天是元旦放假的第一天我也偷下懶,不想寫這麼多了,(本文有待完善,待續 ......)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章