go进阶知识点讲解

interface

底层实现

空interface

在Go语言的源码位置: src\runtime\runtime2.go中

可以看到对于空的interface,其实就是两个指针。第一个rtype类型, 这个就表示类型基本信息,包括类型的大小,对齐信息,类型的编号

type eface struct {    _type *_type        //类型指针    data  unsafe.Pointer  //数据区域指针}

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  //接口带有的函数名}

带方法的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位整数,可以这么表示:

对于s := Stringer(b),可以如下表示:

那么对于s来说

itab中的_type表示的是Stringer这个接口inter中的typ表示的是Binary这个动态类型,fun函数表中存放的就是Binary中实现了String而接口的方法地址。

对于调用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作为返回判断的依据,而不是判断返回的指针

2、不要把为nil的变量赋值给一个interface(推荐

3、判断interface的data字段,忽略type

切片

切片是对数组中一段数据的引用

底层实现

在内存中它有三段数据组成:

指向数据头的指针 ptr

切片的长度 len

切片的容量 cap

长度是索引操作的上界,如:x[i] 。容量是切片操作的上界,如:x[i:j]。

在runtime\slice.go中,我们可以看到, slice的make,copy,grow等函数都在这个文件中实现。

type slice struct {    array unsafe.Pointer    len  int    cap  int}

Go语言提供了内置的copy和append函数来增长切片的容量。

copy方法并不会修改slice的内存模型,仅仅是将某个slice的内容拷贝到另外一个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}

打印结果:

append方法其实重新生成了一个新的数组,然后返回的切片引用了这个新的数组

fmt.Println(*(*sliceA)(unsafe.Pointer(&results))) a := t{"aaa", "ddd"} results = append(results, a) fmt.Println(*(*sliceA)(unsafe.Pointer(&results)))

打印结果:

append具体实现的代码看不到,但过程其实就是判断cap,生成一个新的数组,将old的元素拷贝到新的slice中去。

扩容规则:

如果新的大小是当前大小2倍以上,则大小增长为新大小

否则循环以下操作:如果当前大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小。

典型的坑

1、下面的修改,把切片的引用改掉了,导致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

底层实现

典型的坑

sync.Pool

底层实现

上面我们可以看到pool创建的时候是不能指定大小的,所有sync.Pool的缓存对象数量是没有限制的(只受限于内存),因此使用sync.pool是没办法做到控制缓存对象数量的个数的。另外sync.pool缓存对象的期限是很诡异的,先看一下src/pkg/sync/pool.go里面的一段实现代码:

func init() { 

    runtime_registerPoolCleanup(poolCleanup)  }

可以看到pool包在init的时候注册了一个poolCleanup函数,它会清除所有的pool里面的所有缓存的对象,该函数注册进去之后会在每次gc之前都会调用,因此sync.Pool缓存的期限只是两次gc之间这段时间。

如何在多个goroutine之间使用同一个pool做到高效呢?官方的做法就是尽量减少竞争,因为sync.pool为每个P(对应cpu,不了解的童鞋可以去看看golang的调度模型介绍)都分配了一个子池,如下图:

当执行一个pool的get或者put操作的时候都会先把当前的goroutine固定到某个P的子池上面,然后再对该子池进行操作。每个子池里面有一个私有对象和共享列表对象,私有对象是只有对应的P能够访问,因为一个P同一时间只能执行一个goroutine,因此对私有对象存取操作是不需要加锁的。共享列表是和其他P分享的,因此操作共享列表是需要加锁的。

获取对象过程是:

1)固定到某个P,尝试从私有对象获取,如果私有对象非空则返回该对象,并把私有对象置空;

2)如果私有对象是空的时候,就去当前子池的共享列表获取(需要加锁);

3)如果当前子池的共享列表也是空的,那么就尝试去其他P的子池的共享列表偷取一个(需要加锁);

4)如果其他子池都是空的,最后就用用户指定的New函数产生一个新的对象返回。

可以看到一次get操作最少0次加锁,最大N(N等于MAXPROCS)次加锁。

归还对象的过程:

1)固定到某个P,如果私有对象为空则放到私有对象;

2)否则加入到该P子池的共享列表中(需要加锁)。

可以看到一次put操作最少0次加锁,最多1次加锁。

由于goroutine具体会分配到那个P执行是golang的协程调度系统决定的,因此在MAXPROCS>1的情况下,多goroutine用同一个sync.Pool的话,各个P的子池之间缓存的对象是否平衡以及开销如何是没办法准确衡量的。但如果goroutine数目和缓存的对象数目远远大于MAXPROCS的话,概率上说应该是相对平衡的。

总的来说,sync.Pool的定位不是做类似连接池的东西,它的用途仅仅是增加对象重用的几率,减少gc的负担,而开销方面也不是很便宜的。

典型的坑

nil

“nil”标志符用于表示interface、函数、maps、slices和channels的“零值”。如果你不指定变量的类型,编译器将无法编译你的代码,因为它猜不出具体的类型。

底层实现

典型的坑

new

底层实现

典型的坑

make

底层实现

典型的坑

goroutine

底层实现

典型的坑

channel

底层实现

典型的坑

defer

底层实现

典型的坑

panic & recover

底层实现

典型的坑

GC

底层实现

典型的坑

反射

底层实现

典型的坑

编译过程

底层实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341