美文网首页
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