Go踩坑笔记

Go踩坑笔记

参数绑定

通过网络传输的数据将结构体进行绑定时,字段一定要导出,不然绑定会失败,tag 遵循它该有的原则。

type param struct{
    field1 `json:"filed1"`
    Filed2 
}
// 第一种即使有 tag 解析函数仍然不会解析到结构体的参数上。
// 第二种即使没有 tag 也可以解释到结构体上,按照默认规则,首字母小写进行解析 filed2

Channel 操作

在使用 for range 接收无缓存 channel 的数据时,如果发送完成没有关闭该 channel 可能会发生死锁。

调度机制

func main(){
    runtime.GOMAXPROCS(1)

    go func(){
        for{     
        }
    }()

    time.Sleep(time.Millsecond)
    fmt.Println("ok")
}

以上结果可能会卡住,这是因为 go 的调度器的调度机制造成了,在没有实现抢占式调度之前,该程序不会打印,实现了抢占式调度会打印 ok 并退出。详情可以查看 Golang MPG 模式!

defer 机制

当使用 defer 函数进行资源关闭时,要先进行错误判断在 defer 关闭资源。

file, err := os.Open(path)
if err != nil {
    panic(err)
}
defer file.Close()

v2rayW项目

项目采用 token 验证用户权限,当请求 v2ray 的状态和日志需要使用到 websocket 协议 , 当请求这两个接口时需要验证 token 权限,所以前端请求时将 token 放到了Sec-WebSocket-Protocol 头里到服务器进行验证权限。当没有其他操作时使用这种方式一些浏览器能够正常工作,但是使用 chrome 时,http 不能升级到 websocket,原因是因为服务器没有设置 websocket 的子协议,原因说明

在本项目中,我把每个请求的 token 设置到了 websocket 的子协议中,在进行权限验证。

slice append 扩容机制

扩容大小

在使用 append 函数为 slice 进行扩容时,网上很多文章都说它的扩容是在原 slice 容量小于 1024 时,2倍扩容,在大于或等于 1024 时扩容为 1.25 倍。在 runtime/slice.go处有如下代码:

func growslice(et *_type, old slice, cap int) slice {
    //...

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

    // ...
}

但是就是由于这段代码让我在测试 append 是否真的按这样的方式在做的时候出现了问题

func main(){
    s := make([]int, 1)
    fmt.Println(cap(s)) // cap is 1
    s1 := []int{1, 2, 3, 4} 
    fmt.Println(cap(s1)) // cap is 4
    s = append(s, s1...)
    fmt.Println(cap(s)) // 按上面的代码段得到的新容量应该为 5 但实际是6

    // 其它示例
    a := []byte{1, 0}
    a = append(a, 1, 1, 1)
    fmt.Println(cap(a)) // cap is 8

    b := []int{23, 51}
    b = append(b, 4, 5, 6)
    fmt.Println(cap(b)) // cap is 6

    c := []int32{1, 23}
    c = append(c, 2, 5, 6)
    fmt.Println(cap(c)) // cap is 8

    type D struct{
        age byte
        name string
    }
    d := []D{
        {1,"123"},
        {2,"234"},
    }
    d = append(d,D{4,"456"},D{5,"567"},D{6,"678"})
    fmt.Println(cap(d)) // cap is 5 
}

出现上面的原因在于在 growslice 中后面的代码重新计算了 slice 的容量

func growslice(et *_type, old slice, cap int) slice {
    //...

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

    var overflow bool
    var lenmem, newlenmem, capmem uintptr
    // Specialize for common values of et.size.
    // For 1 we don't need any division/multiplication.
    // For sys.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
    // For powers of 2, use a variable shift.
    switch {
    case et.size == 1:
        lenmem = uintptr(old.len)
        newlenmem = uintptr(cap)
        capmem = roundupsize(uintptr(newcap))
        overflow = uintptr(newcap) > maxAlloc
        newcap = int(capmem)
    case et.size == sys.PtrSize:
        lenmem = uintptr(old.len) * sys.PtrSize
        newlenmem = uintptr(cap) * sys.PtrSize
        capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
        overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
        newcap = int(capmem / sys.PtrSize)
    case isPowerOfTwo(et.size):
        var shift uintptr
        if sys.PtrSize == 8 {
            // Mask shift for better code generation.
            shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
        } else {
            shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
        }
        lenmem = uintptr(old.len) << shift
        newlenmem = uintptr(cap) << shift
        capmem = roundupsize(uintptr(newcap) << shift)
        overflow = uintptr(newcap) > (maxAlloc >> shift)
        newcap = int(capmem >> shift)
    default:
        lenmem = uintptr(old.len) * et.size
        newlenmem = uintptr(cap) * et.size
        capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
        capmem = roundupsize(capmem)
        newcap = int(capmem / et.size)
    }

    // ...
}

et 是 slice 中元素的类型的一种元数据信息,就分析 slice,et 中只需要知道 size 就足够了,size代表的是,元素在计算机所占的字节大小。对于 64 位系统, size 大小为8个字节。round-up,向上取整,roundupsize,向上取一个size。(uintptr(newcap) \* sys.PtrSize)的乘积应该为5*8=40,经过向上取整之后得到了新的所需内存capmem=48,接着所需内存/类型大小int(capmem / sys.PtrSize),得到了新的容量,也就是6.

func main(){
    // 在扩容后赋予另一个变量
    a := make([]int, 4, 8)
    println(a) // [4/8]0xc000094f18
    b := append(a, 3, 3, 3)
    println(b, a) // [7/8]0xc000094f18 [4/8]0xc000094f18 
    a = append(a, 4, 4, 4)
    println(a, b[4]) // [7/8]0xc000094f18 4
}

总结

在通过上面扩容代码段得到新容量后,还要根据 slice 的类型 size,计算出新的容量所需的内存大小 capmem,然后在对 capmem 向上取整,得到新的所需要的内存大小然后除以 size 得到真正的容量大小。

参考

Go语言设计与实现 – 切片

Go slice扩容深度分析


 上一篇
Dart语言笔记.md Dart语言笔记.md
Dart 语言学习笔记简介Google 推出的在任意平台快速构建应用的客户端优化语言,可以编译成 ARM & X64 到任意平台上。 语法结构代码码上来 // 每个应用都有 main 函数 void main(){ prin
2020-08-22
下一篇 
基础知识记录 基础知识记录
基础知识记录Hash 函数hash 也称散列,哈希。基本原理是将任意长度的输入,通过 hash 函数变成固定长度的输出。原始数据映射后的二进制串就是哈希值 Hash 表hash 表是一个存储键值映射的数据结构,它的读写效率在装载因子在正常水
  目录