当前位置:网站首页>Go 语言中常见的坑
Go 语言中常见的坑
2022-08-01 20:27:00 【云满笔记】
这里填写标题
1. Go 语言中常见的坑
这里列举的 Go 语言常见坑都是符合 Go 语言语法的,可以正常的编译,但是可能是运行结果错误,或者是有资源泄漏的风险。
1.1. 可变参数是空接口类型
当参数的可变参数是空接口类型时,传入空接口的切片时需要注意参数展开的问题。
func main() {
var a = []interface{
}{
1, 2, 3}
fmt.Println(a)
fmt.Println(a...)
}
不管是否展开,编译器都无法发现错误,但是输出是不同的:
[1 2 3]
1 2 3
1.2. 数组是值传递
在函数调用参数中,数组是值传递,无法通过修改数组类型的参数返回结果。
func main() {
x := [3]int{
1, 2, 3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr)
}(x)
fmt.Println(x)
}
必要时需要使用切片。
1.3. map 遍历是顺序不固定
map 是一种 hash 表实现,每次遍历的顺序都可能不一样。
func main() {
m := map[string]string{
"1": "1",
"2": "2",
"3": "3",
}
for k, v := range m {
println(k, v)
}
}
1.4. 返回值被屏蔽
在局部作用域中,命名的返回值内同名的局部变量屏蔽:
func Foo() (err error) {
if err := Bar(); err != nil {
return
}
return
}
1.5. recover 必须在 defer 函数中运行
recover 捕获的是祖父级调用时的异常,直接调用时无效:
func main() {
recover()
panic(1)
}
直接 defer 调用也是无效:
func main() {
defer recover()
panic(1)
}
defer 调用时多层嵌套依然无效:
func main() {
defer func() {
func() {
recover() }()
}()
panic(1)
}
必须在 defer 函数中直接调用才有效:
func main() {
defer func() {
recover()
}()
panic(1)
}
1.6. main 函数提前退出
后台 Goroutine 无法保证完成任务。
func main() {
go println("hello")
}
1.7. 通过 Sleep 来回避并发中的问题
休眠并不能保证输出完整的字符串:
func main() {
go println("hello")
time.Sleep(time.Second)
}
类似的还有通过插入调度语句:
func main() {
go println("hello")
runtime.Gosched()
}
1.8. 独占 CPU 导致其它 Goroutine 饿死
Goroutine 是协作式抢占调度,Goroutine 本身不会主动放弃 CPU:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {
} // 占用 CPU
}
解决的方法是在 for 循环加入 runtime.Gosched() 调度函数:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {
runtime.Gosched()
}
}
或者是通过阻塞的方式避免 CPU 占用:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
os.Exit(0)
}()
select{
}
}
1.9. 不同 Goroutine 之间不满足顺序一致性内存模型
因为在不同的 Goroutine, main 函数中无法保证能打印出 hello, world:
var msg string
var done bool
func setup() {
msg = "hello, world"
done = true
}
func main() {
go setup()
for !done {
}
println(msg)
}
解决的办法是用显式同步:
var msg string
var done = make(chan bool)
func setup() {
msg = "hello, world"
done <- true
}
func main() {
go setup()
<-done
println(msg)
}
msg 的写入是在 channel 发送之前,所以能保证打印 hello, world
1.10. 闭包错误引用同一个变量
func main() {
for i := 0; i < 5; i++ {
defer func() {
println(i)
}()
}
}
改进的方法是在每轮迭代中生成一个局部变量:
func main() {
for i := 0; i < 5; i++ {
i := i
defer func() {
println(i)
}()
}
}
或者是通过函数参数传入:
func main() {
for i := 0; i < 5; i++ {
defer func(i int) {
println(i)
}(i)
}
}
1.11. 在循环内部执行 defer 语句
defer 在函数退出时才能执行,在 for 执行 defer 会导致资源延迟释放:
func main() {
for i := 0; i < 5; i++ {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
}
解决的方法可以在 for 中构造一个局部函数,在局部函数内部执行 defer:
func main() {
for i := 0; i < 5; i++ {
func() {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}()
}
}
1.12. 切片会导致整个底层数组被锁定
切片会导致整个底层数组被锁定,底层数组无法释放内存。如果底层数组较大会对内存产生很大的压力。
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = data[:1]
}
// do some thing
}
解决的方法是将结果克隆一份,这样可以释放底层的数组:
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = append([]byte{
}, data[:1]...)
}
// do some thing
}
1.13. 空指针和空接口不等价
比如返回了一个错误指针,但是并不是空的 error 接口:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
1.14. 内存地址会变化
Go 语言中对象的地址可能发生变化,因此指针不能从其它非指针类型的值生成:
func main() {
var x int = 42
var p uintptr = uintptr(unsafe.Pointer(&x))
runtime.GC()
var px *int = (*int)(unsafe.Pointer(p))
println(*px)
}
当内存发送变化的时候,相关的指针会同步更新,但是非指针类型的 uintptr 不会做同步更新。
同理 CGO 中也不能保存 Go 对象地址。
1.15. Goroutine 泄露
Go 语言是带内存自动回收的特性,因此内存一般不会泄漏。但是 Goroutine 确存在泄漏的情况,同时泄漏的 Goroutine 引用的内存同样无法被回收。
func main() {
ch := func() <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
} ()
return ch
}()
for v := range ch {
fmt.Println(v)
if v == 5 {
break
}
}
}
上面的程序中后台 Goroutine 向管道输入自然数序列,main 函数中输出序列。但是当 break 跳出 for 循环的时候,后台 Goroutine 就处于无法被回收的状态了。
我们可以通过 context 包来避免这个问题:
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := func(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
select {
case <- ctx.Done():
return
case ch <- i:
}
}
} ()
return ch
}(ctx)
for v := range ch {
fmt.Println(v)
if v == 5 {
cancel()
break
}
}
}
当 main 函数在 break 跳出循环时,通过调用 cancel() 来通知后台 Goroutine 退出,这样就避免了 Goroutine 的泄漏。
边栏推荐
- 【无标题】
- Little data on how to learn?Jida latest small learning data review, 26 PDF page covers the 269 - page document small data learning theory, method and application are expounded
- 环境变量,进程地址空间
- 【ES】ES2021 我学不动了,这次只学 3 个。
- 面试突击70:什么是粘包和半包?怎么解决?
- 密码学的基础:X.690和对应的BER CER DER编码
- 外骨骼机器人(七):标准步态数据库
- Failed to re-init queues : Illegal queue capacity setting (abs-capacity=0.6) > (abs-maximum-capacity
- 【无标题】
- Acrel-5010重点用能单位能耗在线监测系统在湖南三立集团的应用
猜你喜欢
Remove 360's detection and modification of the default browser
WhatsApp group sending actual combat sharing - WhatsApp Business API account
【Dart】dart之mixin探究
[Personal Work] Remember - Serial Logging Tool
30-day question brushing plan (5)
Zheng Xiangling, Chairman of Tide Pharmaceuticals, won the "2022 Outstanding Influential Entrepreneur Award" Tide Pharmaceuticals won the "Corporate Social Responsibility Model Award"
【个人作品】记之-串口日志记录工具
regular expression
C语言实现-直接插入排序(带图详解)
[Energy Conservation Institute] Comparative analysis of smart small busbar and column head cabinet solutions in data room
随机推荐
Compose实战-实现一个带下拉加载更多功能的LazyColumn
LTE时域、频域资源
Interview assault 70: what is the glue bag and a bag?How to solve?
用户身份标识与账号体系实践
Application of Acrel-5010 online monitoring system for key energy consumption unit energy consumption in Hunan Sanli Group
The graphic details Eureka's caching mechanism/level 3 cache
58:第五章:开发admin管理服务:11:开发【管理员人脸登录,接口】;(未实测)(使用了阿里AI人脸识别)(演示了,使用RestTemplate实现接口调用接口;)
Custom command to get focus
57: Chapter 5: Develop admin management services: 10: Develop [get files from MongoDB's GridFS, interface]; (from GridFS, get the SOP of files) (Do not use MongoDB's service, you can exclude its autom
New graduate students, great experience in reading English literature, worthy of your collection
Remove 360's detection and modification of the default browser
【七夕特别篇】七夕已至,让爱闪耀
实用新型专利和发明专利的区别?秒懂!
4.1 配置Mysql与注册登录模块
面试突击70:什么是粘包和半包?怎么解决?
密码学的基础:X.690和对应的BER CER DER编码
【无标题】
Zheng Xiangling, Chairman of Tide Pharmaceuticals, won the "2022 Outstanding Influential Entrepreneur Award" Tide Pharmaceuticals won the "Corporate Social Responsibility Model Award"
kingbaseV8R3和postgreSQL哪个版本最接近?
ARTS_202207W2