美文网首页
go进阶知识点讲解

go进阶知识点讲解

作者: youngfn | 来源:发表于2019-05-17 11:29 被阅读0次

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

底层实现

典型的坑

反射

底层实现

典型的坑

编译过程

底层实现

相关文章

  • go进阶知识点讲解

    interface 底层实现 空interface 在Go语言的源码位置: src\runtime\runtime...

  • go语言学习-从基础到实战到源码分析

    收集的一些go语言学习资料,有go基础学习系列,go项目实战,go进阶-go源码分析,还有go的一些书籍,go的架...

  • 从零开始学网贷·进阶篇(二)

    在上一篇中,主要讲解了一下对网贷平台认识的四个进阶基础知识点,在本篇中,笔者将继续深入把进阶世界的另一面展现给大家...

  • android线程池的使用

    最近一直在看《android开发艺术探索》,这本书是一本进阶书,书中讲解的好多知识从源码的角度对一个知识点...

  • 临时

    Vue 常考进阶知识点 这一章节我们将来学习 Vue 的一些经常考到的进阶知识点。这些知识点相对而言理解起来会很有...

  • Go 语言程序设计(3)

    stacker.go 示例代码: stack.go 示例代码: 知识点: go 内置基础类型:布尔类型: bool...

  • Golang资料整理

    视频 郝林-Go语言第一课 Go编程基础 Go Web 基础 Go名库讲解 社区 官网要翻墙 Github--Go...

  • go| go channel讲解1

    date: 2018-6-10 15:14:50title: go| go channel讲解 1descript...

  • 我的战舰地图逻辑教程

    基础知识 - 简书 实例讲解-推箱子(入门) 实例讲解-打Boss(入门) - 简书 实例讲解-弹球(进阶) - 简书

  • Spring Boot 知识点速记

    本文是学习2小时学会Spring Boot和Spring Boot进阶之Web进阶的SpringBoot常用知识点...

网友评论

      本文标题:go进阶知识点讲解

      本文链接:https://www.haomeiwen.com/subject/knunaqtx.html