美文网首页
Go自学笔记(持续更新...)

Go自学笔记(持续更新...)

作者: 皇甫LG | 来源:发表于2020-04-20 19:37 被阅读0次

    一、各数据类型内存布局

    • Sizeof(uint8) = 1 字节 = 8byte
        +-+
        |1|
        +-+
         |
        +-+-+-+-+-+-+-+-+
        |0|0|0|0|0|0|0|1|
        +-+-+-+-+-+-+-+-+
    
    • Sizeof(rune) = 4
        +-----+
        |  4  |
        +-----+
           |
           |
        +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 
        |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|1|0|0|
        +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 
    
    • Sizeof(int64) = 8
        +-----+
        |  8  |
        +-----+
           |
           |
        +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 
        |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0|
        +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+     
        +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 
        |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|0|0|0|0| |0|0|0|0|1|0|0|0|
        +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 
    
    • Sizeof(float64)=8
        +-----+
        |  8  |
        +-----+
           |
           |
          同上
    
    • Sizeof(string) = 8|16 区分32/64位服务器
    var s string = "hello"
        +---------+---------+
        | Pointer | len=5   |   # pointer和len各占4|8个字节
        +---------+---------+
           |
           |
        +---+---+---+---+---+
        | h | e | l | l | o |    # [5]byte
        +---+---+---+---+---+
    
    • struct
    struct{a byte; b byte; c int32} = {1,2,3}
        +---+---+---+---+---+---+---+---+
        | 1 | 2 | 0 | 0 | 3 | 0 | 0 | 0 |    # 内存对齐原则
        +---+---+---+---+---+---+---+---+
    
    struct{a *int; b int}
        +-----------+-----+
        | pointer a |  b  |   
        +-----------+-----+
            |
            |
            +-----+
            | int |   
            +-----+
    
    
    • Sizeof(slice)=24
    x = []int{0,1,2,3,4,5,6,7}
        +---------+---------+---------+
        | Pointer | len=8   | cap=8   |    # 各占8个字节
        +---------+---------+---------+
           |
           |
            +---+---+---+---+---+---+---+---+
            | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |    # [8]int
            +---+---+---+---+---+---+---+---+
                |
                |
                +---------+---------+---------+
                | Pointer | len=2   | cap=5   | # x[1:3:6]
                +---------+---------+---------+           
        
    
    • Sizeof(interface{}) = 16
    var in interface{}    
        +---------+---------+
        |  *itab  |  *data  |   # 各占8个字节
        +---------+---------+
           |           |
           |           |
            +------+    +------+
            | Itab |    | data | 
            +------+    +------+
    
    • new
    var n = new([3]int)
        +---------+
        | pointer |   #占8个字节
        +---------+
           |
           |
           +---+---+---+
           |0  | 0 | 0 |
           +---+---+---+
    
    
    • make
    slice = make([]int,1,3)
        +---------+---------+---------+
        | pointer |  len=1  |  cap=3  |    # 各占8个字节
        +---------+---------+---------+
           |
           |
           +---+---+---+
           |0  | 0 | 0 |    # [3]int
           +---+---+---+
        
        
    channel = make(chan int);
        +---------+
        | pointer |     # 实际返回的是一个指针包装对象
        +---------+ 
           |
           |
           +---------------+
           | chan.c  Hchan |
           +---------------+
    

    二、基础概念总结(持续更新...)

    • 1、unsafe.sizeof总是在编译期就进行求值,而不是在运行时,这意味着,sizeof的返回值可以赋值给常量。字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是4/ 8 个字节,所以字符串类型大小为 8/16 个字节(32bit/64bit)
      字符串表示一个包含0个或者多个字符序列的串,在一个字符串内部,每个字符都表示成一个或者多个UTF-8编码的字节。

    • 2、string 类型的值是只读的二进制 byte slice,不可按照索引号直接修改。字符串类型的零值是空串 "" if str =="" {true}

    • 3、在 C/C++ 中,数组(名)是指针。将数组作为参数传进函数时,相当于传递了数组内存地址的引用,在函数内部会改变该数组的值。
      在 Go 中,数组是值。作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的。 如果想要修改数组参数,方法有两个: 1)直接传递指向这个数组的指针类型。2)使用slice:即使函数内部得到的是slice的值拷贝,但是依旧会更新slice的原始数据。

    • 4、switch 语句中的 case 代码块会默认带上 break。

    • 5、以小写字母开头的字段成员是无法被外部直接访问的。在进行json、xml、gob等格式的encode操作时,这些私有字段容易被忽略,导出时得到零值。

    func StrEncodeTest() {
        type MyData struct {
            One int
            two string
        }
        in := MyData{1, "data"}
        fmt.Printf("%#v\n", in)
    
        en, _ := json.Marshal(in)
        fmt.Println(string(en))
    
        var out MyData
        json.Unmarshal(en, &out)
        fmt.Printf("%#v\n", out)
    }
    
    • 6、slice 中隐藏的数据,从 slice 中重新切出新 slice 时,新 slice 会引用原 slice 的底层数组。如果跳了这个坑,程序可能会分配大量的临时 slice 来指向原底层数组的部分数据,将导致难以预料的内存使用。

    • 7、select语句类似switch,但是select会公平随机执行选择一个可运行的case执行,如果没有可行对case,执行default。如果没有default,则就会阻塞。

    • 8、使用const关键字定义常量,并不能使用:=标识符。

    • 9、从Slice移除元素

    # 移除单个元素
    func (s *Slice) SliceRemove(value int) {
        for i, v := range *s {
            if v == value {
                *s = append((*s)[:i], (*s)[i+1:]...)   ##slice后面‘...’ 由于元素类型相同,因此append会提示使用..., 使用两个参数即可
            }
        }
    }
    
    # 移除多个元素
    *s = append((*s)[:begin-1], (*s)[end:]...) 
    
    • 10、golang中数据类型(channel, complex, 函数) 不能转化为有效的JSON文本。

    • 11、关于channel的特性,下面说法正确的是(ABCD)

    A. 给一个 nil channel 发送数据,造成永远阻塞
    B. 从一个 nil channel 接收数据,造成永远阻塞
    C. 给一个已经关闭的 channel 发送数据,引起 panic
    D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
    
    • 12、For 语句中的迭代变量与闭包函数陷阱
    # for语句中的迭代变量在每次迭代中都会崇勇,即for中创建闭包函数接受到的参数始终是同一个值,在goroutine开始执行时都会得到同一个迭代值。
    
    fun main(){
        data := []string{"one", "two", "three"}
        
        for _, v := range data {
            go func(){ //闭包
                fmt.Println(v)
            }() 
        }
        time.Sleep(3*time.Second)
    }
    
    # 输出 three,three,three
    
    # 解决方法:在for 内部实用局部变量保存迭代值,在传参:
       for _, v :=range data {
           vCopy := v
           go func(){
               fmt.Println(vCopy)
           }()
       }
    
    • 13、Map 线程安全
      在map并发情况下, 只读线程是安全的,同时读写是线程不安全的,严重时导致程序退出,错误信息如下:
      fatal error: concurrent map read and map write。这个错误信息并不是每次都会遇到,并发访问的协成数量比较大时必然会出现。
      所以建议使用sync.RWMutex读写锁实现对map的并发访问。
    package main
    
    import "sync"
    
    type SafeMap struct {
        sync.RWMutex
        Map map[int]int
    }
    
    func main() {
        safeMap := newSafeMap(10)
    
        for i := 0; i < 100000; i++ {
            go safeMap.writeMap(i, i)
            go safeMap.readMap(i)
        }
    
    }
    
    func newSafeMap(size int) *SafeMap {
        sm := new(SafeMap)
        sm.Map = make(map[int]int)
        return sm
    
    }
    
    func (sm *SafeMap) readMap(key int) int {
        sm.RLock()
        value := sm.Map[key]
        sm.RUnlock()
        return value
    }
    
    func (sm *SafeMap) writeMap(key int, value int) {
        sm.Lock()
        sm.Map[key] = value
        sm.Unlock()
    }
    

    通过sync.RWMutex读写锁控制达到安全访问map,此时会有一定的性能损耗。
    建议使用==sync.Map== 是go1.9版本中提供的一种效率比较高并发安全。

    • 14、go方法集 仅仅影响接口实现和方法表达式的转化,如果指针接收者实现一个接口,那么只有指向那个类型的指针才能实现对应的接口。 如果使用值接收者来实现一个接口,那么类型的值和指针都可以实现对应的接口。
    a) func (u *user) notify() { show(&u) }
    
    b1) func (u user) notify() { show(u) }
    b2) func (u user) notify() { show(&u) }
    
    • 14、panic仅有最有一个可以被recover捕获。
    • 15、Array、Slice、Map、Set说明
    Array:
        数组是固定长度的数据类型,包含相同类型的连续元素,内存分配是连续的。初始值为0;
        一个数组可以被赋值给任意相同类型(长度和元素类型)的数组。
        建议在函数中不要使用数组作为参数传递。
    
    Slice:
        一种动态数组,底层内存是连续分配的。内存结构【指向底层数组的指针|长度|容量】;
        var slice []int  // nil slice 
        slice :=make([]int,0) //empty slice
        slice :=[]int{}    //empty slice
        实用append方法可以对slice进行扩容和删除。
        函数使用slice作为参数传递是比较廉价的,sizeof(slice)=16字节。
        
    Map:
        Map是一种无序的K-V集合。由于是无序的,因此无法决定迭代时返回顺序。
        在函数使用map作为参数传递时,不是传递的map拷贝,而是引用类型,因此会修改原map的内容的。详情请参考:[轻松理解Go函数传参内幕](https://www.jianshu.com/p/198d9961b76a)
    
    Set:
        go本身不提供set,需要自己实现。参考:https://github.com/deckarep/golang-set
        
    总结:array是slice和map的底层结构。slice有cap限制,但是可以通过append增加元素,map没有cap限制,可以无限增加。因此内建函数 cap 只能作用在 slice 上。在函数间传递slice和map是比较廉价的,它们不会传递底层数组的拷贝。
    
    • 16、go 内存逃逸分析
    go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就分配在栈,反之则分配在堆。
    
    • 17、go routine 调度解析
    M(machine): 代表着真正的执行计算资源,可以认为它就是os thread(系统线程)。
    P(processor): 表示逻辑processor,是线程M的执行的上下文。
    G(goroutine): 调度系统的最基本单位goroutine,存储了goroutine的执行stack信息、goroutine状态以及goroutine的任务函数等。
    
    • 18、如何定位golang性能问题
    panic 调用栈
    pprof
    火焰图(配合压测)
    使用go run -race 或者 go build -race 来进行竞争检测
    查看系统 磁盘IO/网络IO/内存占用/CPU 占用(配合压测)
    
    • 19、golang的runtime机制
    Runtime 负责管理任务调度,垃圾收集及运行环境。一个重要的组成部分goroutinue scheduler,负责追踪调度每个routine运行,实际上是从应用程序的process所属的thread pool中分配一个thread来执行这个goroutine,每个gotine只有分配到一个os thread才能运行。
    编译后的可执行文件,从运行角度看是有2个部分组成,1)用户代码,2)runtime。 runtime通过接口函数调用来管理goroutine,chanal等其他一些高级功能,从用户代码发起的调用操作系统的API都会被runtime拦截并处理。
    

    相关文章

      网友评论

          本文标题:Go自学笔记(持续更新...)

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