golang interface

golang的interface剖析

 
背景:
golang的interface是一種satisfied式的。A類只要實現了IA interface定義的方法,A就satisfied了接口IA。更抽象一層,若是某些設計上須要一些更抽象的共性,好比print各種型,這時須要使用reflect機制,reflect實質上就是將interface的實現暴露了一部分給應用代碼。要理解reflect,須要深刻了解interface。
 
go的interface是一種隱式的interface,但golang的類型是編譯階段定的,是static的,如:
1
2
3
type  MyInt int
var  i int
var  j MyInt
雖然MyInt底層就是int,但在編譯器角度看,i的類型是int,j的類型是MyInt,是靜態、不一致的。二者要賦值必需要進行類型轉換。
即便是interface,就語言角度來看也是靜態的。如:
 
1
var  r io.Reader
 
無論r後面用什麼來初始化,它的類型老是io.Reader。
更進一步,對於空的interface,也是如此。
 
記住go語言類型是靜態這一點,對於理解interface/reflect很重要。
 
看一例:
1
2
3
4
5
6
var  r io.Reader
tty, err := os.OpenFile( "/dev/tty" , os.O_RDWR, 0)
if  err != nil {
     return  nil, err
}
r = tty

到這裏,r的類型是什麼?r的類型仍然是interface io.Reader,只是r = tty這一句,隱含了一個類型轉換,將tty轉成了io.Reader。html

 
interface的實現:
做爲一門編程語言,對方法的處理通常分爲兩種類型:一是將全部方法組織在一個表格裏,靜態地調用(C++, java);二是調用時動態查找方法(python, smalltalk, js)。
而go語言是二者的結合:雖然有table,可是是須要在運行時計算的table。
 
以下例:
Binary類實現了兩個方法,String()和Get()
 
1
2
3
4
5
6
7
8
type  Binary uint64
func  (i Binary) String() string {
     return  strconv.Uitob64(i.Get(), 2)
}
  
func  (i Binary) Get() uint64 {
     return  uint64(i)
}
由於它實現了String(),按照golang的隱式方法實現來看,Binary satisfied了Stringer接口。所以它能夠賦值: s:=Stringer(b)。
以此爲例來講明下interface的實現:
 
interface的內存組織如圖:
一個interface值由兩個指針組成,第一個指向一個interface table,叫 itable。itable開頭是一些描述類型的元字段,後面是一串方法。注意這個方法是interface自己的方法,並不是其dynamic value(Binary)的方法。即這裏只有String()方法,而沒有Get方法。但這個方法的實現確定是具體類的方法,這裏就是Binary的方法。
 
當這個interface無方法時,itable能夠省略,直接指向一個type便可。
 
另外一個指針data指向dynamic value的一個拷貝,這裏則是b的一份拷貝。也就是,給interface賦值時,會在堆上分配內存,用於存放拷貝的值。
一樣,當值自己只有一個字長時,這個指針也能夠省略。
 
 
一個interface的初始值是兩個nil。好比,
1
var  w io.Writer
這時,tab和data都是nil。interface是否爲nil取決於itable字段。因此不必定data爲nil就是nil,判斷時要額外注意。
 
這樣,像這樣的代碼:
1
2
3
4
5
6
switch  v := any.( type ) {
case  int:
     return  strconv.Itoa(v)
case  float:
     return  strconv.Ftoa(v,  'g' , -1)
}
 
其實是any這個interface取了  any. tab->type。
 
而interface的函數調用實際上就變成了:
s.tab->fun[0](s.data)。第一個參數即自身類型指針。
 
itable的生成:
itable的生成是理解interface的關鍵。
如剛開始處提的,爲了支持go語言這種接口間僅經過方法來聯繫的特性,是沒有辦法像C++同樣,在編譯時預先生成一個method table的,只能在運行時生成。所以,天然的,全部的實體類型都必須有一個包含此類型全部方法的「類型描述符」(type description structure);而interface類型也一樣有一個相似的描述符,包含了全部的方法。
這樣,interface賦值時,計算interface對象的itable時,須要對兩種類型的方法列表進行遍歷對比。如後面代碼所示,這種計算只須要進行一次,並且優化成了O(m+n)。
 
可見,interface與itable之間的關係不是獨立的,而是與interface具體的value類型有關。 即(interface類型, 具體類型)->itable。
1   var  any interface {}  // initialized elsewhere
2   s := any.(Stringer)  // dynamic conversion
3   for  i := 0; i < 100; i++ {
4       fmt.Println(s.String())
5   }
itable的計算不須要到函數調用時進行,只須要在interface賦值時進行便可,如上第2行,不須要在第4行進行。
 
最後,看一些實現代碼:
如下是上面圖中的兩個字段。
143 type  iface struct  {
144     tab  *itab     // 指南itable
145     data unsafe.Pointer     // 指向真實數據
146 }
再看itab的實現:
1
2
3
4
5
6
7
8
617  type  itab  struct  {
618     inter  *interfacetype
619     _type  *_type
620     link   *itab
621     bad    int32
622     unused int32
623     fun    [1]uintptr  // variable sized
624 }

  

可見,它使用一個疑似鏈表的東西,能夠猜這是用做hash表的拉鍊。
前兩個字段應該是用來表達具體的interface類型和實際擁有的值的類型的,即一個itable的key。(上文提到的(interface類型, 具體類型) )
1
2
3
4
5
6
7
8
9
10
11
310  type  imethod  struct  {
311     name nameOff
312     ityp typeOff
313 }
314
315  type  interfacetype  struct  {
316     typ     _type
317     pkgpath name
318     mhdr    []imethod
319 }
320

 

interfacetype若有若干imethod,能夠猜測這是表達interface定義的方法數據結構。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
28  type  _type  struct  {
29     size       uintptr
30     ptrdata    uintptr  // size of memory prefix holding all pointers
31     hash       uint32
32     tflag      tflag
33     align      uint8
34     fieldalign uint8
35     kind       uint8
36     alg        *typeAlg
37      // gcdata stores the GC type data for the garbage collector.
38      // If the KindGCProg bit is set in kind, gcdata is a GC program.
39      // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
40     gcdata    *byte
41     str       nameOff
42     ptrToThis typeOff
43 }
 
對於_type,可見裏面有gc的東西,應該就是具體的類型了。這裏有個hash字段,itable實現就是掛在一個全局的hash table中。hash時用到了這個字段:
22 func  itabhash(inter *interfacetype, typ *_type) uint32 {
23     // compiler has provided some good hash codes for us.
24     h := inter.typ.hash
25     h += 17 * typ.hash
26     // TODO(rsc): h += 23 * x.mhash ?
27     return  h % hashSize
28 }

 

可見,這裏有個把interface類型與具體類型之間的信息結合起來作一個hash的過程,這個hash就是上述的itab的存儲地點,itab中的link就是hash中的拉鍊。
 
回到itab,看取一個itab的邏輯:
若是發生了typeassert或是interface的賦值(強轉),須要臨時計算一個itab。這時會先在hash表中找,找不到纔會真實計算。
44     h := itabhash(inter, typ)
45
46     // look twice - once without lock, once with.
47     // common case will be no lock contention.
48     var  m *itab
49     var  locked int
50     for  locked = 0; locked < 2; locked++ {
51         if  locked != 0 {
52             lock(&ifaceLock)
53         }
54         for  m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {
55             if  m.inter == inter && m._type == typ {
71                 return  m    // 找到了前面計算過的itab
72             }
73         }
74     }
75    // 沒有找到,生成一個,並加入到itab的hash中。
76     m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
77     m.inter = inter
78     m._type = typ
79     additab(m, true, canfail)

  

 
這個hash是個全局變量:
13 const  (
14     hashSize = 1009
15 )
16
17 var  (
18     ifaceLock mutex // lock for accessing hash
19     hash      [hashSize]*itab
20 )
最後,看一下如何生成itab:
92     // both inter and typ have method sorted by name,
  93     // and interface names are unique,
  94     // so can iterate over both in lock step;
  95     // the loop is O(ni+nt) not O(ni*nt).       // 按name排序過的,所以這裏的匹配只須要O(ni+nt)
  99     j := 0
100     for  k := 0; k < ni; k++ {
101         i := &inter.mhdr[k]
102         itype := inter.typ.typeOff(i.ityp)
103         name := inter.typ.nameOff(i.name)
104         iname := name.name()
109         for  ; j < nt; j++ {
110             t := &xmhdr[j]
111             tname := typ.nameOff(t.name)
112             if  typ.typeOff(t.mtyp) == itype && tname.name() == iname {
118                     if  m != nil {
119                         ifn := typ.textOff(t.ifn)
120                         *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn // 找到匹配,將實際類型的方法填入itab的fun
121                     }
122                     goto  nextimethod
123                 }
124             }
125         }
135     nextimethod:
136     }
140     h := itabhash(inter, typ)             //插入上面的全局hash
141     m.link = hash[h]
142     atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
143 }

到這裏,interface的數據結構的框架。java

reflection實質上是將interface背後的實現暴露了一部分給應用代碼,使應用程序可使用interface實現的一些內容。只要理解了interface的實現,reflect就好理解了。如reflect.typeof(i)返回interface i的type,Valueof返回value.
 
參考:
摘抄自他人博客
相關文章
相關標籤/搜索