interface
在Go語言的源碼位置: src\runtime\runtime2.go中
可以看到對于空的interface,其實就是兩個指針边翁。第一個rtype類型, 這個就表示類型基本信息翎承,包括類型的大小,對齊信息符匾,類型的編號
type eface struct {? ? _type *_type? ? ? ? //類型指針? ? data? unsafe.Pointer? //數(shù)據(jù)區(qū)域指針}
type _type struct {? ? size? ? ? uintptr
? ? ptrdata? ? uintptr // size of memory prefix holding all pointers? ? hash? ? ? uint32
? ? tflag? ? ? tflag
? ? align? ? ? uint8
? ? fieldalign uint8
? ? kind? ? ? uint8
? ? alg? ? ? ? *typeAlg
? ? // gcdata stores the GC type data for the garbage collector.? ? // If the KindGCProg bit is set in kind, gcdata is a GC program.? ? // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.? ? gcdata? ? *byte? ? str? ? ? nameOff
? ? ptrToThis typeOff}
對于有方法的interface來說叨咖,也是兩個指針
第一個itab中存放了類型信息,還有一個fun表示方法表啊胶。
type iface struct {? ? tab? *itab
? ? data unsafe.Pointer}type itab struct {? ? inter? *interfacetype
? ? _type? *_type
? ? link? *itab
? ? bad? ? int32
? ? inhash int32? ? ? // has this itab been added to hash?? ? fun? ? [1]uintptr // variable sized}type interfacetype struct {? ? typ? ? _type
? ? pkgpath name
? ? mhdr? ? []imethod? //接口帶有的函數(shù)名}
帶方法的interface舉例:
package mainimport ("strconv""fmt")type Stringer interface {? ? String() string}type Binary uint64
func (i Binary) String() string {? ? return strconv.FormatUint(i.Get(), 2)}func (i Binary) Get() uint64 {? ? return uint64(i)}func main() {? ? b := Binary(200)? ? fmt.Println(b.String())? ? s := Stringer(b)? ? fmt.Println(s.String())}
對于Binary甸各,作為一個64位整數(shù),可以這么表示:
對于s := Stringer(b)焰坪,可以如下表示:
那么對于s來說
itab中的_type表示的是Stringer這個接口趣倾,inter中的typ表示的是Binary這個動態(tài)類型,fun函數(shù)表中存放的就是Binary中實現(xiàn)了String而接口的方法地址琳彩。
對于調(diào)用s.String()方法誊酌,其實就是 s.itab->fun[0]部凑。
type face struct{? ? _type unsafe.Pointer? ? data unsafe.Pointer}type fake struct {? ? a string}var test interface{} = nilvar b *fake = nilvar test2 interface{} = b
func main()? {? ? if test != nil {? ? ? ? fmt.Println("that's not what i want!!!!!! ", *(*face)(unsafe.Pointer(&test)) )? ? }else{? ? ? ? fmt.Println("wonderful result", *(*face)(unsafe.Pointer(&test)))? ? }? ? if test2 != nil {? ? ? ? fmt.Println("that's not what i want!!!!!!", *(*face)(unsafe.Pointer(&test2)))? ? }else{? ? ? ? fmt.Println("wonderful result", *(*face)(unsafe.Pointer(&test2)))? ? }}
采用interface作為返回值類型時碧浊,避坑思路:
1涂邀、利用error作為返回判斷的依據(jù),而不是判斷返回的指針
2箱锐、不要把為nil的變量賦值給一個interface(推薦)
3比勉、判斷interface的data字段,忽略type
切片是對數(shù)組中一段數(shù)據(jù)的引用驹止。
在內(nèi)存中它有三段數(shù)據(jù)組成:
指向數(shù)據(jù)頭的指針 ptr
切片的長度 len
切片的容量 cap
長度是索引操作的上界浩聋,如:x[i] 。容量是切片操作的上界臊恋,如:x[i:j]衣洁。
在runtime\slice.go中,我們可以看到抖仅, slice的make坊夫,copy,grow等函數(shù)都在這個文件中實現(xiàn)撤卢。
type slice struct {? ? array unsafe.Pointer? ? len? int? ? cap? int}
Go語言提供了內(nèi)置的copy和append函數(shù)來增長切片的容量环凿。
copy方法并不會修改slice的內(nèi)存模型,僅僅是將某個slice的內(nèi)容拷貝到另外一個slice中去放吩。
func (tGen *tInfo) Copy []t { if tGen.ts == nil {? ? return nil } newField := make([]t, len(tGen.ts)) fmt.Println(*(*sliceA)(unsafe.Pointer(&newField))) copy(newField, tGen.ts) fmt.Println(*(*sliceA)(unsafe.Pointer(&newField))) return newField}
打印結(jié)果:
append方法其實重新生成了一個新的數(shù)組智听,然后返回的切片引用了這個新的數(shù)組
fmt.Println(*(*sliceA)(unsafe.Pointer(&results))) a := t{"aaa", "ddd"} results = append(results, a) fmt.Println(*(*sliceA)(unsafe.Pointer(&results)))
打印結(jié)果:
append具體實現(xiàn)的代碼看不到,但過程其實就是判斷cap渡紫,生成一個新的數(shù)組到推,將old的元素拷貝到新的slice中去。
擴容規(guī)則:
如果新的大小是當(dāng)前大小2倍以上惕澎,則大小增長為新大小
否則循環(huán)以下操作:如果當(dāng)前大小小于1024环肘,按每次2倍增長,否則每次按當(dāng)前大小1/4增長集灌。直到增長的大小超過或等于新大小。
1复哆、下面的修改欣喧,把切片的引用改掉了,導(dǎo)致newField引用被修改梯找,出錯
// KoratCopy_taiRanges_Fastfunc (tGen *tInfo) Copy(newSlice0 []t) {? ? for index1, oldStruct1 := range tGen.ts {? ? ? ? newSlice0[index1] = oldStruct1
? ? ? ? oldStruct1.CopyFast(&newSlice0[index1])? ? }}// KoratCopyFastfunc (tGen *t) CopyFast(newStruct *t) {? ? *newStruct = *tGen}func (tGen *tInfo) KCopy []t {? ? if tGen.ts == nil {? ? ? ? return nil? ? }? ? newField := make([]t, len(tGen.t))? ? tGen.Copy(newField)? ? return newField}
func (tGen *tfo) KCopy []t {? ? if tGen.taiRanges == nil {? ? ? ? return nil? ? }? ? newField := make([]t, len(tGen.ts))? ? newField = tGen.ts
? ? return newField}
上面我們可以看到pool創(chuàng)建的時候是不能指定大小的唆阿,所有sync.Pool的緩存對象數(shù)量是沒有限制的(只受限于內(nèi)存),因此使用sync.pool是沒辦法做到控制緩存對象數(shù)量的個數(shù)的锈锤。另外sync.pool緩存對象的期限是很詭異的驯鳖,先看一下src/pkg/sync/pool.go里面的一段實現(xiàn)代碼:
func init() {?
? ? runtime_registerPoolCleanup(poolCleanup)? }
可以看到pool包在init的時候注冊了一個poolCleanup函數(shù)闲询,它會清除所有的pool里面的所有緩存的對象,該函數(shù)注冊進(jìn)去之后會在每次gc之前都會調(diào)用浅辙,因此sync.Pool緩存的期限只是兩次gc之間這段時間扭弧。
如何在多個goroutine之間使用同一個pool做到高效呢?官方的做法就是盡量減少競爭记舆,因為sync.pool為每個P(對應(yīng)cpu鸽捻,不了解的童鞋可以去看看golang的調(diào)度模型介紹)都分配了一個子池,如下圖:
當(dāng)執(zhí)行一個pool的get或者put操作的時候都會先把當(dāng)前的goroutine固定到某個P的子池上面泽腮,然后再對該子池進(jìn)行操作御蒲。每個子池里面有一個私有對象和共享列表對象,私有對象是只有對應(yīng)的P能夠訪問诊赊,因為一個P同一時間只能執(zhí)行一個goroutine厚满,因此對私有對象存取操作是不需要加鎖的。共享列表是和其他P分享的碧磅,因此操作共享列表是需要加鎖的碘箍。
獲取對象過程是:
1)固定到某個P,嘗試從私有對象獲取续崖,如果私有對象非空則返回該對象敲街,并把私有對象置空;
2)如果私有對象是空的時候严望,就去當(dāng)前子池的共享列表獲榷嗤А(需要加鎖);
3)如果當(dāng)前子池的共享列表也是空的像吻,那么就嘗試去其他P的子池的共享列表偷取一個(需要加鎖)峻黍;
4)如果其他子池都是空的,最后就用用戶指定的New函數(shù)產(chǎn)生一個新的對象返回拨匆。
可以看到一次get操作最少0次加鎖姆涩,最大N(N等于MAXPROCS)次加鎖。
歸還對象的過程:
1)固定到某個P惭每,如果私有對象為空則放到私有對象骨饿;
2)否則加入到該P子池的共享列表中(需要加鎖)。
可以看到一次put操作最少0次加鎖台腥,最多1次加鎖宏赘。
由于goroutine具體會分配到那個P執(zhí)行是golang的協(xié)程調(diào)度系統(tǒng)決定的,因此在MAXPROCS>1的情況下黎侈,多goroutine用同一個sync.Pool的話察署,各個P的子池之間緩存的對象是否平衡以及開銷如何是沒辦法準(zhǔn)確衡量的。但如果goroutine數(shù)目和緩存的對象數(shù)目遠(yuǎn)遠(yuǎn)大于MAXPROCS的話峻汉,概率上說應(yīng)該是相對平衡的贴汪。
總的來說脐往,sync.Pool的定位不是做類似連接池的東西,它的用途僅僅是增加對象重用的幾率扳埂,減少gc的負(fù)擔(dān)业簿,而開銷方面也不是很便宜的。
“nil”標(biāo)志符用于表示interface聂喇、函數(shù)辖源、maps、slices和channels的“零值”希太。如果你不指定變量的類型克饶,編譯器將無法編譯你的代碼,因為它猜不出具體的類型誊辉。