go語(yǔ)言中接口的應(yīng)用

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)go語(yǔ)言中接口的應(yīng)用,以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

創(chuàng)新互聯(lián)公司專注于噶爾網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供噶爾營(yíng)銷(xiāo)型網(wǎng)站建設(shè),噶爾網(wǎng)站制作、噶爾網(wǎng)頁(yè)設(shè)計(jì)、噶爾網(wǎng)站官網(wǎng)定制、微信小程序開(kāi)發(fā)服務(wù),打造噶爾網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供噶爾網(wǎng)站排名全網(wǎng)營(yíng)銷(xiāo)落地服務(wù)。

1. 接口的基本使用

golang中的interface本身也是一種類(lèi)型,它代表的是一個(gè)方法的集合。任何類(lèi)型只要實(shí)現(xiàn)了接口中聲明的所有方法,那么該類(lèi)就實(shí)現(xiàn)了該接口。與其他語(yǔ)言不同,golang并不需要顯式聲明類(lèi)型實(shí)現(xiàn)了某個(gè)接口,而是由編譯器和runtime進(jìn)行檢查。

聲明

 type 接口名 interface{
    方法1
    方法2
    ...
   方法n 
}
type 接口名 interface {
    已聲明接口名1
    ...
    已聲明接口名n
}
type iface interface{
    tab *itab
    data unsafe.Pointer
}

接口自身也是一種結(jié)構(gòu)類(lèi)型,只是編譯器對(duì)其做了很多限制:

● 不能有字段

● 不能定義自己的方法

● 只能聲明方法,不能實(shí)現(xiàn)

● 可嵌入其他接口類(lèi)型

package main
    import (
        "fmt"
    )
    // 定義一個(gè)接口
    type People interface {
        ReturnName() string
    }
    // 定義一個(gè)結(jié)構(gòu)體
    type Student struct {
        Name string
    }
    // 定義結(jié)構(gòu)體的一個(gè)方法。
    // 突然發(fā)現(xiàn)這個(gè)方法同接口People的所有方法(就一個(gè)),此時(shí)可直接認(rèn)為結(jié)構(gòu)體Student實(shí)現(xiàn)了接口People
    func (s Student) ReturnName() string {
        return s.Name
    }
    func main() {
        cbs := Student{Name:"小明"}
        var a People
        // 因?yàn)镾tudents實(shí)現(xiàn)了接口所以直接賦值沒(méi)問(wèn)題
        // 如果沒(méi)實(shí)現(xiàn)會(huì)報(bào)錯(cuò):cannot use cbs (type Student) as type People in assignment:Student does not implement People (missing ReturnName method)
        a = cbs       
        name := a.ReturnName() 
        fmt.Println(name) // 輸出"小明"
    }

如果一個(gè)接口不包含任何方法,那么它就是一個(gè)空接口(empty interface),所有類(lèi)型都符合empty interface的定義,因此任何類(lèi)型都能轉(zhuǎn)換成empty interface。

接口的值簡(jiǎn)單來(lái)說(shuō),是由兩部分組成的,就是類(lèi)型和數(shù)據(jù),判斷兩個(gè)接口是相等,就是看他們的這兩部分是否相等;另外類(lèi)型和數(shù)據(jù)都為nil才代表接口是nil。

var a interface{} 
var b interface{} = (*int)(nil)
fmt.Println(a == nil, b == nil) //true false

2. 接口嵌套

像匿名字段那樣嵌入其他接口。目標(biāo)類(lèi)型方法集中必須擁有包含嵌入接口方法在內(nèi)的全部方法才算實(shí)現(xiàn)了該接口。嵌入其他接口類(lèi)型相當(dāng)于將其聲明的方法集中導(dǎo)入。這就要求不能有同名方法,不能嵌入自身或循環(huán)嵌入。

type stringer interfaceP{
     string() string
}

type tester interface {
    stringer
    test()
}    

type data struct{}

func (*data) test() {}

func (data) string () string {
    return ""
}

func main() {
    var d data 
    var t tester = &d 
    t.test()
    println(t.string())
}

超集接口變量可隱式轉(zhuǎn)換為子集,反過(guò)來(lái)不行。

3. 接口的實(shí)現(xiàn)

golang的接口檢測(cè)既有靜態(tài)部分,也有動(dòng)態(tài)部分。

● 靜態(tài)部分

對(duì)于具體類(lèi)型(concrete type,包括自定義類(lèi)型) -> interface,編譯器生成對(duì)應(yīng)的itab放到ELF的.rodata段,后續(xù)要獲取itab時(shí),直接把指針指向存在.rodata的相關(guān)偏移地址即可。具體實(shí)現(xiàn)可以看golang的提交日志CL 20901、CL 20902。
對(duì)于interface->具體類(lèi)型(concrete type,包括自定義類(lèi)型),編譯器提取相關(guān)字段進(jìn)行比較,并生成值

● 動(dòng)態(tài)部分

在runtime中會(huì)有一個(gè)全局的hash表,記錄了相應(yīng)type->interface類(lèi)型轉(zhuǎn)換的itab,進(jìn)行轉(zhuǎn)換時(shí)候,先到hash表中查,如果有就返回成功;如果沒(méi)有,就檢查這兩種類(lèi)型能否轉(zhuǎn)換,能就插入到hash表中返回成功,不能就返回失敗。注意這里的hash表不是go中的map,而是一個(gè)最原始的使用數(shù)組的hash表,使用開(kāi)放地址法來(lái)解決沖突。主要是interface <-> interface(接口賦值給接口、接口轉(zhuǎn)換成另一接口)使用到動(dòng)態(tài)生產(chǎn)itab。

interface的結(jié)構(gòu)如下:

go語(yǔ)言中接口的應(yīng)用

接口類(lèi)型的結(jié)構(gòu)interfacetype

type interfacetype struct {
    typ     _type   
    pkgpath name   //記錄定義接口的包名
    mhdr    []imethod  //一個(gè)imethod切片,記錄接口中定義的那些函數(shù)。
}
// imethod表示接口類(lèi)型上的方法
type imethod struct {
    name nameOff // name of method
    typ  typeOff // .(*FuncType) underneath
}

nameOff 和 typeOff 類(lèi)型是 int32 ,這兩個(gè)值是鏈接器負(fù)責(zé)嵌入的,相對(duì)于可執(zhí)行文件的元信息的偏移量。元信息會(huì)在運(yùn)行期,加載到 runtime.moduledata 結(jié)構(gòu)體中。

4. 接口值的結(jié)構(gòu)iface和eface

為了性能,golang專門(mén)分了兩種interface,eface和iface,eface就是空接口,iface就是有方法的接口。

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

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

type itab struct {
    inter *interfacetype   //inter接口類(lèi)型
    _type *_type   //_type數(shù)據(jù)類(lèi)型
    hash  uint32  //_type.hash的副本。用于類(lèi)型開(kāi)關(guān)。 hash哈希的方法
    _     [4]byte
    fun   [1]uintptr  // 大小可變。 fun [0] == 0表示_type未實(shí)現(xiàn)inter。 fun函數(shù)地址占位符
}

iface結(jié)構(gòu)體中的data是用來(lái)存儲(chǔ)實(shí)際數(shù)據(jù)的,runtime會(huì)申請(qǐng)一塊新的內(nèi)存,把數(shù)據(jù)考到那,然后data指向這塊新的內(nèi)存。

itab中的hash方法拷貝自_type.hash;fun是一個(gè)大小為1的uintptr數(shù)組,當(dāng)fun[0]為0時(shí),說(shuō)明_type并沒(méi)有實(shí)現(xiàn)該接口,當(dāng)有實(shí)現(xiàn)接口時(shí),fun存放了第一個(gè)接口方法的地址,其他方法一次往下存放,這里就簡(jiǎn)單用空間換時(shí)間,其實(shí)方法都在_type字段中能找到,實(shí)際在這記錄下,每次調(diào)用的時(shí)候就不用動(dòng)態(tài)查找了。

4.1 全局的itab table

iface.go:

const itabInitSize = 512

// 注意:如果更改這些字段,請(qǐng)?jiān)趇tabAdd的mallocgc調(diào)用中更改公式。
type itabTableType struct {
    size    uintptr             // 條目數(shù)組的長(zhǎng)度。始終為2的冪。
    count   uintptr             // 當(dāng)前已填寫(xiě)的條目數(shù)。
    entries [itabInitSize]*itab // really [size] large
}

可以看出這個(gè)全局的itabTable是用數(shù)組在存儲(chǔ)的,size記錄數(shù)組的大小,總是2的次冪。count記錄數(shù)組中已使用了多少。entries是一個(gè)*itab數(shù)組,初始大小是512。

5. 接口類(lèi)型轉(zhuǎn)換

把一個(gè)具體的值,賦值給接口,會(huì)調(diào)用conv系列函數(shù),例如空接口調(diào)用convT2E系列、非空接口調(diào)用convT2I系列,為了性能考慮,很多特例的convT2I64、convT2Estring諸如此類(lèi),避免了typedmemmove的調(diào)用。

  func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
    if raceenabled {
        raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
    }
    if msanenabled {
        msanread(elem, t.size)
    }
    x := mallocgc(t.size, t, true)
    // TODO: 我們分配一個(gè)清零的對(duì)象只是為了用實(shí)際數(shù)據(jù)覆蓋它。
    //確定如何避免歸零。同樣在下面的convT2Eslice,convT2I,convT2Islice中。
    typedmemmove(t, x, elem)
    e._type = t
    e.data = x
    return
}

func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
    t := tab._type
    if raceenabled {
        raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I))
    }
    if msanenabled {
        msanread(elem, t.size)
    }
    x := mallocgc(t.size, t, true)
    typedmemmove(t, x, elem)
    i.tab = tab
    i.data = x
    return
}

func convT2I16(tab *itab, val uint16) (i iface) {
    t := tab._type
    var x unsafe.Pointer
    if val == 0 {
        x = unsafe.Pointer(&zeroVal[0])
    } else {
        x = mallocgc(2, t, false)
        *(*uint16)(x) = val
    }
    i.tab = tab
    i.data = x
    return
}

func convI2I(inter *interfacetype, i iface) (r iface) {
    tab := i.tab
    if tab == nil {
        return
    }
    if tab.inter == inter {
        r.tab = tab
        r.data = i.data
        return
    }
    r.tab = getitab(inter, tab._type, false)
    r.data = i.data
    return
}

可以看出:

● 具體類(lèi)型轉(zhuǎn)空接口,_type字段直接復(fù)制源的type;mallocgc一個(gè)新內(nèi)存,把值復(fù)制過(guò)去,data再指向這塊內(nèi)存。

● 具體類(lèi)型轉(zhuǎn)非空接口,入?yún)ab是編譯器生成的填進(jìn)去的,接口指向同一個(gè)入?yún)ab指向的itab;mallocgc一個(gè)新內(nèi)存,把值復(fù)制過(guò)去,data再指向這塊內(nèi)存。

● 對(duì)于接口轉(zhuǎn)接口,itab是調(diào)用getitab函數(shù)去獲取的,而不是編譯器傳入的。

對(duì)于那些特定類(lèi)型的值,如果是零值,那么不會(huì)mallocgc一塊新內(nèi)存,data會(huì)指向zeroVal[0]。

5.1 接口轉(zhuǎn)接口

  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 {
        // 顯式轉(zhuǎn)換需要非nil接口值。
        panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
    }
    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
}

我們看到有兩種用法:

● 返回值是一個(gè)時(shí),不能轉(zhuǎn)換就panic。

● 返回值是兩個(gè)時(shí),第二個(gè)返回值標(biāo)記能否轉(zhuǎn)換成功

此外,data復(fù)制的是指針,不會(huì)完整拷貝值。每次都malloc一塊內(nèi)存,那么性能會(huì)很差,因此,對(duì)于一些類(lèi)型,golang的編譯器做了優(yōu)化。

5.2 接口轉(zhuǎn)具體類(lèi)型

接口判斷是否轉(zhuǎn)換成具體類(lèi)型,是編譯器生成好的代碼去做的。我們看個(gè)empty interface轉(zhuǎn)換成具體類(lèi)型的例子:

  var EFace interface{}
var j int

func F4(i int) int{
    EFace = I
    j = EFace.(int)
    return j
}

func main() {
    F4(10)
}

反匯編:

go build -gcflags '-N -l' -o tmp build.go
go tool objdump -s "main.F4" tmp

可以看匯編代碼:

MOVQ main.EFace(SB), CX       
//CX = EFace.typ2 LEAQ type.*+60128(SB), DX    
//DX = &type.int3 CMPQ DX, CX.

可以看到empty interface轉(zhuǎn)具體類(lèi)型,是編譯器生成好對(duì)比代碼,比較具體類(lèi)型和空接口是不是同一個(gè)type,而不是調(diào)用某個(gè)函數(shù)在運(yùn)行時(shí)動(dòng)態(tài)對(duì)比。

5.3 非空接口類(lèi)型轉(zhuǎn)換

var tf Tester
var t testStruct

func F4() int{
    t := tf.(testStruct)
    return t.i
}

func main() {
    F4()
}
//反匯編
MOVQ main.tf(SB), CX   // CX = tf.tab(.inter.typ)
LEAQ go.itab.main.testStruct,main.Tester(SB), DX // DX = <testStruct,Tester>對(duì)應(yīng)的&itab(.inter.typ)
CMPQ DX, CX //

可以看到,非空接口轉(zhuǎn)具體類(lèi)型,也是編譯器生成的代碼,比較是不是同一個(gè)itab,而不是調(diào)用某個(gè)函數(shù)在運(yùn)行時(shí)動(dòng)態(tài)對(duì)比。

6. 獲取itab的流程

golang interface的核心邏輯就在這,在get的時(shí)候,不僅僅會(huì)從itabTalbe中查找,還可能會(huì)創(chuàng)建插入,itabTable使用容量超過(guò)75%還會(huì)擴(kuò)容??聪麓a:

 func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
    if len(inter.mhdr) == 0 {
        throw("internal error - misuse of itab")
    }

    // 簡(jiǎn)單的情況
    if typ.tflag&tflagUncommon == 0 {
        if canfail {
            return nil
        }
        name := inter.typ.nameOff(inter.mhdr[0].name)
        panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
    }

    var m *itab

    //首先,查看現(xiàn)有表以查看是否可以找到所需的itab。
    //這是迄今為止最常見(jiàn)的情況,因此請(qǐng)不要使用鎖。
    //使用atomic確保我們看到該線程完成的所有先前寫(xiě)入更新itabTable字段(在itabAdd中使用atomic.Storep)。
    t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
    if m = t.find(inter, typ); m != nil {
        goto finish
    }

    // 未找到。抓住鎖,然后重試。
    lock(&itabLock)
    if m = itabTable.find(inter, typ); m != nil {
        unlock(&itabLock)
        goto finish
    }

    // 條目尚不存在。進(jìn)行新輸入并添加。
    m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
    m.inter = inter
    m._type = typ
    m.init()
    itabAdd(m)
    unlock(&itabLock)
finish:
    if m.fun[0] != 0 {
        return m
    }
    if canfail {
        return nil
    }
    //僅當(dāng)轉(zhuǎn)換時(shí)才會(huì)發(fā)生,使用ok形式已經(jīng)完成一次,我們得到了一個(gè)緩存的否定結(jié)果。
    //緩存的結(jié)果不會(huì)記錄,缺少接口函數(shù),因此初始化再次獲取itab,以獲取缺少的函數(shù)名稱。
    panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

流程如下:

●  先用t保存全局itabTable的地址,然后使用t.find去查找,這樣是為了防止查找過(guò)程中,itabTable被替換導(dǎo)致查找錯(cuò)誤。

●  如果沒(méi)找到,那么就會(huì)上鎖,然后使用itabTable.find去查找,這樣是因?yàn)樵诘谝徊讲檎业耐瑫r(shí),另外一個(gè)協(xié)程寫(xiě)入,可能導(dǎo)致實(shí)際存在卻查找不到,這時(shí)上鎖避免itabTable被替換,然后直接在itaTable中查找。

●  再?zèng)]找到,說(shuō)明確實(shí)沒(méi)有,那么就根據(jù)接口類(lèi)型、數(shù)據(jù)類(lèi)型,去生成一個(gè)新的itab,然后插入到itabTable中,這里可能會(huì)導(dǎo)致hash表擴(kuò)容,如果數(shù)據(jù)類(lèi)型并沒(méi)有實(shí)現(xiàn)接口,那么根據(jù)調(diào)用方式,該報(bào)錯(cuò)報(bào)錯(cuò),該panic panic。

這里我們可以看到申請(qǐng)新的itab空間時(shí),內(nèi)存空間的大小是unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize,參照前面接受的結(jié)構(gòu),len(inter.mhdr)就是接口定義的方法數(shù)量,因?yàn)樽侄蝔un是一個(gè)大小為1的數(shù)組,所以len(inter.mhdr)-1,在fun字段下面其實(shí)隱藏了其他方法接口地址。

6.1 在itabTable中查找itab find

func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
    // 編譯器為我們提供了一些很好的哈希碼。
    return uintptr(inter.typ.hash ^ typ.hash)
}

   // find在t中找到給定的接口/類(lèi)型對(duì)。
   // 如果不存在給定的接口/類(lèi)型對(duì),則返回nil。
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
    // 使用二次探測(cè)實(shí)現(xiàn)。
     //探測(cè)順序?yàn)閔(i)= h0 + i *(i + 1)/ 2 mod 2 ^ k。
     //我們保證使用此探測(cè)序列擊中所有表?xiàng)l目。
    mask := t.size - 1
    h := itabHashFunc(inter, typ) & mask
    for i := uintptr(1); ; i++ {
        p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
        // 在這里使用atomic read,所以如果我們看到m!= nil,我們也會(huì)看到m字段的初始化。
        // m := *p
        m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
        if m == nil {
            return nil
        }
        if m.inter == inter && m._type == typ {
            return m
        }
        h += I
        h &= mask
    }
}

從注釋可以看到,golang使用的開(kāi)放地址探測(cè)法,用的是公式h(i) = h0 + i*(i+1)/2 mod 2^k,h0是根據(jù)接口類(lèi)型和數(shù)據(jù)類(lèi)型的hash字段算出來(lái)的。以前的版本是額外使用一個(gè)link字段去連到下一個(gè)slot,那樣會(huì)有額外的存儲(chǔ),性能也會(huì)差寫(xiě),在1.11中我們看到有了改進(jìn)。

6.2 檢查并生成itab init

// init用所有代碼指針填充m.fun數(shù)組m.inter / m._type對(duì)。 如果該類(lèi)型未實(shí)現(xiàn)該接口,將m.fun [0]設(shè)置為0,并返回缺少的接口函數(shù)的名稱。
//可以在同一m上多次調(diào)用此函數(shù),即使同時(shí)調(diào)用也可以。
func (m *itab) init() string {
    inter := m.inter
    typ := m._type
    x := typ.uncommon()

    // inter和typ都有按名稱排序的方法,
     //并且接口名稱是唯一的,
     //因此可以在鎖定步驟中對(duì)兩者進(jìn)行迭代;
     //循環(huán)是O(ni + nt)而不是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 ""
}

這個(gè)方法會(huì)檢查interface和type的方法是否匹配,即type有沒(méi)有實(shí)現(xiàn)interface。假如interface有n中方法,type有m中方法,那么匹配的時(shí)間復(fù)雜度是O(n x m),由于interface、type的方法都按字典序排,所以O(shè)(n+m)的時(shí)間復(fù)雜度可以匹配完。在檢測(cè)的過(guò)程中,匹配上了,依次往fun字段寫(xiě)入type中對(duì)應(yīng)方法的地址。如果有一個(gè)方法沒(méi)有匹配上,那么就設(shè)置fun[0]為0,在外層調(diào)用會(huì)檢查fun[0]==0,即type并沒(méi)有實(shí)現(xiàn)interface。

這里我們還可以看到golang中continue的特殊用法,要直接continue到外層的循環(huán)中,那么就在那一層的循環(huán)上加個(gè)標(biāo)簽,然后continue 標(biāo)簽。

6.3 把itab插入到itabTable中 itabAdd

 // itabAdd將給定的itab添加到itab哈希表中。
//必須保持itabLock。
func itabAdd(m *itab) {
    // 設(shè)置了mallocing時(shí),錯(cuò)誤可能導(dǎo)致調(diào)用此方法,通常是因?yàn)檫@是在恐慌時(shí)調(diào)用的。
    //可靠地崩潰,而不是僅在需要增長(zhǎng)時(shí)崩潰哈希表。
    if getg().m.mallocing != 0 {
        throw("malloc deadlock")
    }

    t := itabTable
    if t.count >= 3*(t.size/4) { // 75% 負(fù)載系數(shù)
        // 增長(zhǎng)哈希表。
        // t2 = new(itabTableType)+一些其他條目我們?nèi)鲋e并告訴malloc我們想要無(wú)指針的內(nèi)存,因?yàn)樗兄赶虻闹刀疾辉诙阎小?
        t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
        t2.size = t.size * 2

        // 復(fù)制條目。
        //注意:在復(fù)制時(shí),其他線程可能會(huì)尋找itab和找不到它。沒(méi)關(guān)系,他們將嘗試獲取Itab鎖,因此請(qǐng)等到復(fù)制完成。
        if t2.count != t.count {
            throw("mismatched count during itab table copy")
        }
        // 發(fā)布新的哈希表。使用原子寫(xiě)入:請(qǐng)參閱getitab中的注釋。
        atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
        // 采用新表作為我們自己的表。
        t = itabTable
        // 注意:舊表可以在此處進(jìn)行GC處理。
    }
    t.add(m)
}
// add將給定的itab添加到itab表t中。
//必須保持itabLock。
func (t *itabTableType) add(m *itab) {
    //請(qǐng)參閱注釋中的有關(guān)探查序列的注釋。
    //將新的itab插入探針序列的第一個(gè)空位。
    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 {
            //給定的itab可以在多個(gè)模塊中使用并且由于全局符號(hào)解析的工作方式,
            //指向itab的代碼可能已經(jīng)插入了全局“哈?!?。
            return
        }
        if m2 == nil {
            // 在這里使用原子寫(xiě),所以如果讀者看到m,它也會(huì)看到正確初始化的m字段。
            // NoWB正常,因?yàn)閙不在堆內(nèi)存中。
            // *p = m
            atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
            t.count++
            return
        }
        h += I
        h &= mask
    }
}

可以看到,當(dāng)hash表使用達(dá)到75%或以上時(shí),就會(huì)進(jìn)行擴(kuò)容,容量是原來(lái)的2倍,申請(qǐng)完空間,就會(huì)把老表中的數(shù)據(jù)插入到新的hash表中。然后使itabTable指向新的表,最后把新的itab插入到新表中。

上述就是小編為大家分享的go語(yǔ)言中接口的應(yīng)用了,如果您也有類(lèi)似的疑惑,不妨參照上述方法進(jìn)行嘗試。如果想了解更多相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊。

分享名稱:go語(yǔ)言中接口的應(yīng)用
轉(zhuǎn)載源于:http://muchs.cn/article12/pgdsdc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供電子商務(wù)外貿(mào)建站、App開(kāi)發(fā)、GoogleApp設(shè)計(jì)、建站公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁(yè)設(shè)計(jì)公司