注意点
避免切片内存泄露
切片操作并不会复制底层的数据。底层的数组会被保存在内存中,直到它不再被引用。但是有时候可能会因为一个小的内存引用而导致底层整个数组处于被使用的状态,这会延迟自动内存回收器对底层数组的回收。
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return regexp.MustCompile("[0-9]+").Find(b)
}
这段代码返回的[]byte指向保存整个文件的数组。因为切片引用了整个原始数组,导致自动垃圾回收器不能及时释放底层数组的空间。一个小的需求可能导致需要长时间保存整个文件数据。这虽然这并不是传统意义上的内存泄漏,但是可能会拖慢系统的整体性能。
要修复这个问题,可以将感兴趣的数据复制到一个新的切片中(数据的传值是Go语言编程的一个哲学,虽然传值有一定的代价,但是换取的好处是切断了对原始数据的依赖):
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = regexp.MustCompile("[0-9]+").Find(b)
return append([]byte{}, b...)
}
类似的问题,在删除切片元素时可能会遇到。假设切片里存放的是指针对象,那么下面删除末尾的元素后,被删除的元素依然被切片底层数组引用,从而导致不能及时被自动垃圾回收器回收(这要依赖回收器的实现方式):
var a []*int{ ... }
a = a[:len(a)-1] // 被删除的最后一个元素依然被引用, 可能导致GC操作被阻碍
保险的方式是先将需要自动内存回收的元素设置为nil,保证自动回收器可以发现需要回收的对象,然后再进行切片的删除操作:
var a []*int{ ... }
a[len(a)-1] = nil // GC回收最后一个元素内存
a = a[:len(a)-1] // 从切片删除最后一个元素
当然,如果切片存在的周期很短的话,可以不用刻意处理这个问题。因为如果切片本身已经可以被GC回收的话,切片对应的每个元素自然也就是可以被回收的了。
匿名的成员方法
type Cache struct {
m map[string]string
sync.Mutex
}
func (p *Cache) Lookup(key string) string {
p.Lock()
defer p.Unlock()
return p.m[key]
}
Cache结构体类型通过嵌入一个匿名的sync.Mutex来继承它的Lock和Unlock方法. 但是在调用p.Lock()和p.Unlock()时, p并不是Lock和Unlock方法的真正接收者, 而是会将它们展开为p.Mutex.Lock()和p.Mutex.Unlock()调用. 这种展开是编译期完成的, 并没有运行时代价
协程(g)和系统线程(s)
启动栈大小:g->2kb or 4kb s->2mb
最大栈大小:g->1gb s->2mb
Go的运行时还包含了其自己的调度器,这个调度器使用了一些技术手段,可以在n个操作系统线程上多工调度m个Goroutine。Go调度器的工作和内核的调度是相似的,但是这个调度器只关注单独的Go程序中的Goroutine。Goroutine采用的是半抢占式的协作调度,只有在当前Goroutine发生阻塞时才会导致调度;同时发生在用户态,调度器会根据具体函数只保存必要的寄存器,切换的代价要比系统线程低得多。运行时有一个runtime.GOMAXPROCS变量,用于控制当前运行正常非阻塞Goroutine的系统线程数目。
在Go语言中启动一个Goroutine不仅和调用函数一样简单,而且Goroutine之间调度代价也很低,这些因素极大地促进了并发编程的流行和发展。
互斥锁和原子操作
原子操作配合互斥锁可以实现非常高效的单件模式。互斥锁的代价比普通整数的原子读写高很多,在性能敏感的地方可以增加一个数字型的标志位,通过原子检测标志位状态降低互斥锁的使用次数来提高性能。
sync.Once就是这样实现的
并发和并行
并发关注的是程序的设计
并行关注的是程序的运行
不要通过共享内存来通信,而应通过通信来共享内存
数组是值传递,必要时使用切片
table-driven 开发
//if switch实例化
func entry() {
var bi BusinessInstance
switch businessType {
case TravelBusiness:
bi = travelorder.New()
case MarketBusiness:
bi = marketorder.New()
default:
return errors.New("not supported business")
}
}
//table 实例化
var businessInstanceMap = map[int]BusinessInstance {
TravelBusiness : travelorder.New(),
MarketBusiness : marketorder.New(),
}
func entry() {
bi := businessInstanceMap[businessType]
}
闭包错误引用同一个变量,需要变量的显式传递
func main() {
for i := 0; i < 5; i++ {
defer func(i int) {
println(i)
}(i)
}
}
竞争检查
2个goroutine对一个变量可能同时修改,产生竞争,使用 go run -race main.go来检查
swagger生成文档
字节序
计算机字节序和网络字节序 字节序 就是多字节数据类型 (int, float 等)在内存中的存储顺序。可分为大端序,低地址端存放高位字节;小端序与之相反,低地址端存放低位字节
在计算机内部,小端序被广泛应用于现代性 CPU 内部存储数据;而在其他场景譬如网络传输和文件存储使用大端序
内部存储使用小端序,可以利于转化,比如int32转为int64,不需要移动字节,直接补充0即可
gob包
如果字段中有interface类型,需要注册类型,不然解码的时候不知道这是什么类型
type P struct {
X, Y, Z int
Name interface{}
}
type Inner struct {
Test int
}
------
var network bytes.Buffer // Stand-in for a network connection
enc := gob.NewEncoder(&network) // Will write to network.
gob.Register(Inner{}) //作用是告诉Name的interface可能是Inner类型,不然系统报错
inner := Inner{1}
err := enc.Encode(P{1,2,3, inner})
if err != nil {
log.Fatal("encode error:", err)
}
go vet test
go vet ./... //错误检查,静态检查等
go test ./... //测试
go build -race . //竞态检查
解决golang https请求提示x509: certificate signed by unknown authority
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
go 逃逸分析
- 变量大小不确定
- 变量类型不确定
- 变量分配的内存超过用户栈最大值
- 暴露给了外部指针
Context包的注意事项
ctx, cancel := context.WithTimeout(parentCtx, time.Second * 2)
defer cancel()
//使用 Timeout 会导致内部使用 time.AfterFunc,从而会导致 context 在计时器到时之前都不会被垃圾回收。
// 在建立之后,立即 defer cancel() 是一个好习惯。
uint不能直接相减
var a,b uint
a = 18
b = 20
fmt.Println(a-b) //18446744073709551614
switch语句在case子句的选择上是具有唯一性的
//编译不通过
value3 := [...]int8{0, 1, 2, 3, 4, 5, 6}
switch value3[4] {
case 0, 1, 2: //存在2
fmt.Println("0 or 1 or 2")
case 2, 3, 4://有存在2
fmt.Println("2 or 3 or 4")
case 4, 5, 6:
fmt.Println("4 or 5 or 6")
}
strings.builder的7点
通过构建底层[]byte来存储字符串,String()的时候,直接操作字节数组指针,节省内存拷贝
- 和字节数组一样,先定义容量Grow(n int)
- 不要拷贝
- 不支持并行操作
并发访问内存或者缓存,使用groupcache下的singleflight
多个相同的key,只有一个可以通过访问,其他等待结果
make和new
- 共同点:都是分配内存空间的
make
用于为特定类型chan
、slice
、map
分配并初始化内存,返回初始化后的值,而不是指针。new
用于为变量分配内存,并返回指向该内存的指针,且该内存区域会被初始化为零值(例如整数为 0,布尔为 false,指针为 nil),可以用于所有类型。
结构体比较
- 结构体是相同的,但是结构体属性中有不可以比较的类型,如map,slice,则结构体不能用==比较,可以使用reflect.DeepEqual进行比较。
- 只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关
// sn3与sn1就不是相同的结构体了,不能比较。
sn1 := struct {
age int
name string
}{age: 11, name: "qq"}
sn3:= struct {
name string
age int
}{age:11, name:"qq"}
对已经关闭的的chan进行读写,会怎么样?为什么?
- 读已经关闭的chan能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。
- 如果chan关闭前,buffer内有元素还未读,会正确读到chan内的值,且返回的第二个bool值(是否读成功)为true。
- 如果chan关闭前,buffer内有元素已经被读完,chan内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个bool值一直为false。
- 写已经关闭的chan会panic
defer 顺序
后进先出:
数组和切片的区别
- 数组是一个长度固定的数据类型,其长度在定义时就已经确定,不能动态改变;切片是一个长度可变的数据类型,其长度在定义时可以为空,也可以指定一个初始长度。
- 数组的内存空间是在定义时分配的,其大小是固定的;切片的内存空间是在运行时动态分配的,其大小是可变的。
- 当数组作为函数参数时,函数操作的是数组的一个副本,不会影响原始数组;当切片作为函数参数时,函数操作的是切片的引用,会影响原始切片。
- 切片还有容量的概念,它指的是分配的内存空间。
私有仓库
# 报错:github.com/hashicorp/memberlist: git.****.cn/****/xxx/****-@v1.0.1: reading https://goproxy.cn/git.****.cn/****/xxx/****-.mod: 404 Not Found
# 设置私有仓库的git地址
go env -w GOPRIVATE="git.xxx.cn"
# 允许设置不安全访问,配置后可请求到 http 地址的仓库
go env -w GOINSECURE="git.xxx.cn"
# 设置请求该地址不需要代理,即GOPROXY
go env -w GONOPROXY="git.xxx.cn"
# 设置不验证sum包的签名
go env -w GONOSUMDB="git.xxx.cn"
# 项目名称必须是仓库地址
#拉取仓库go get git.xxx.cn/name/xxx@v1.0.2
-
鉴权问题 ```shell fatal: could not read Username for ‘https://git.**.cn’: No such device or address # cat >~/.netrc«EOF machine git.xxxx.cn login ${GITLAB_USER_NAME} password ${GITLAB_PASSWORD} EOF
cat >~/.gitconfig«EOF [user] name = xxx email = ${GITLAB_USER_NAME} EOF
```