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 得到真正的容量大小。