go進(jìn)階知識點講解

interface

底層實現(xiàn)

空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

對于有方法的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]部凑。

典型的坑

1、interface類型變量和nil比較

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ù)的引用驹止。

底層實現(xiàn)

在內(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}

map

底層實現(xiàn)

典型的坑

sync.Pool

底層實現(xiàn)

上面我們可以看到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

“nil”標(biāo)志符用于表示interface聂喇、函數(shù)辖源、maps、slices和channels的“零值”希太。如果你不指定變量的類型克饶,編譯器將無法編譯你的代碼,因為它猜不出具體的類型誊辉。

底層實現(xiàn)

典型的坑

new

底層實現(xiàn)

典型的坑

make

底層實現(xiàn)

典型的坑

goroutine

底層實現(xiàn)

典型的坑

channel

底層實現(xiàn)

典型的坑

defer

底層實現(xiàn)

典型的坑

panic & recover

底層實現(xiàn)

典型的坑

GC

底層實現(xiàn)

典型的坑

反射

底層實現(xiàn)

典型的坑

編譯過程

底層實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末矾湃,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子堕澄,更是在濱河造成了極大的恐慌邀跃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛙紫,死亡現(xiàn)場離奇詭異拍屑,居然都是意外死亡,警方通過查閱死者的電腦和手機坑傅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門僵驰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人唁毒,你說我怎么就攤上這事蒜茴。” “怎么了浆西?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵粉私,是天一觀的道長。 經(jīng)常有香客問我近零,道長诺核,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任久信,我火速辦了婚禮猪瞬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘入篮。我一直安慰自己,他們只是感情好幌甘,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布潮售。 她就那樣靜靜地躺著痊项,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酥诽。 梳的紋絲不亂的頭發(fā)上鞍泉,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音肮帐,去河邊找鬼咖驮。 笑死,一個胖子當(dāng)著我的面吹牛训枢,可吹牛的內(nèi)容都是我干的托修。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼恒界,長吁一口氣:“原來是場噩夢啊……” “哼睦刃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起十酣,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤涩拙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耸采,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兴泥,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年虾宇,在試婚紗的時候發(fā)現(xiàn)自己被綠了搓彻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡文留,死狀恐怖好唯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情燥翅,我是刑警寧澤骑篙,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站森书,受9級特大地震影響靶端,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凛膏,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一杨名、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猖毫,春花似錦台谍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坞生。三九已至,卻和暖如春掷伙,著一層夾襖步出監(jiān)牢的瞬間是己,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工任柜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卒废,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓宙地,卻偏偏與公主長得像摔认,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绸栅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345