第
Go语言中常见的坑以及高性能编程技巧分享
目录背景本文相关代码的验证环境一.常见的坑1.1数据结构1.2Go语言特性相关二.高性能Go编程2.1数据结构2.2内存管理2.3并发编程
背景
代码的稳健性、高性能、可读性是我们每一位coder必须去追求的目标,也是coding的基本功。
本文结合Go语言的特性,以及自己在写Go项目中做的总结,从Go常见的数据结构、内存管理、并发等方面做了相关总结
本文相关代码的验证环境
GOARCH=arm64GOOS=darwinGOVERSION=go1.16.15
一.常见的坑
1.1数据结构
1.在函数调用过程中,数组是值传递pass-by-value,必要时必须使用slice。因为slice是引用类型,在slice传参时,只会复制slice的Data指针和len、cap,形参和实参的slice使用的是同一个底层数组。
2.map是一种hash表实现,每次遍历的顺序都可能不一样。
3.切片会导致整个数组被锁定,导致底层数组无法及时释放内存,如果底层数组过大,会对内存产生极大的压力。
错误示例:
functest(){
fileHeaderMap:=make(map[string][]byte)
fori:=0;ii++{
name:=/data/test/file_+strconv.Itoa(i)
data,err:=ioutil.ReadFile(name)
iferr!=nil{
fmt.Println(err)
continue
fileHeaderMap[name]=data[:1]
//dosomething
}
正确示例:
解决办法是将结果克隆一份,这样可以释放底层数组
functest(){
fileHeaderMap:=make(map[string][]byte)
fori:=0;ii++{
name:=/data/test/file_+strconv.Itoa(i)
data,err:=ioutil.ReadFile(name)
iferr!=nil{
fmt.Println(err)
continue
fileHeaderMap[name]=append([]byte{},data[:1]...)
//dosomething
}
4.当函数的可变参数是空接口时,传入空接口的切片时需要注意参数展开的问题
funcmain(){
vara=[]interface{}{111,222,333}
fmt.Println(a)
fmt.Println(a...)
//不管是否展开,编译器都会编译通过,但是输出是不同的:
//print:
[111222333]
111222333
5.对于切片的操作,会操作同一个底层数组,因此对于一个切片的修改操作,会影响到整个数组。另外由于string在go中是immutable,对于同一个字符串的两个变量,Go做了内存优化,会使用的相同的底层数组
funcmain(){
slice:=[]int{1,2,3,4,5}
slice1:=slice[:2]
slice2:=slice[:4]
fmt.Println(slice:,slice,,slice1:,slice1,,slice2:,slice2)
slice2[0]=6
fmt.Println(slice:,slice,,slice1:,slice1,,slice2:,slice2)
string1:=gohelloword
string2:=gohelloword
fmt.Printf(string1dataaddr:%d\n,(*reflect.StringHeader)(unsafe.Pointer(string1)).Data)
fmt.Printf(string2dataaddr:%d\n,(*reflect.StringHeader)(unsafe.Pointer(string2)).Data)
//print:
slice:[12345],slice1:[12],slice2:[1234]
slice:[