Golang可能会踩的58个坑之高级篇
前言
Go 是一门简单有趣的编程语言,与其他语言一样,在使用时不免会遇到很多坑,不过它们大多不是 Go 本身的设计缺陷。如果你刚从其他语言转到 Go,那这篇文章里的坑多半会踩到。
如果花时间学习官方 doc、wiki、讨论邮件列表、 Rob Pike 的大量文章以及 Go 的源码,会发现这篇文章中的坑是很常见的,跳过这些坑,能减少大量调试代码的时间。
高级篇:52-58
52.使用指针作为方法的 receiver
只要值是可寻址的,就可以在值上直接调用指针方法。即是对一个方法,它的 receiver 是指针就足矣。
但不是所有值都是可寻址的,比如 map 类型的元素、通过 interface 引用的变量:
type data struct {name string
}type printer interface {print()
}func (p *data) print() {fmt.Println("name: ", p.name)
}func main() {d1 := data{"one"}d1.print() // d1 变量可寻址,可直接调用指针 receiver 的方法var in printer = data{"two"}in.print() // 类型不匹配m := map[string]data{"x": data{"three"},}m["x"].print() // m["x"] 是不可寻址的 // 变动频繁
}
cannot use data literal (type data) as type printer in assignment:
data does not implement printer (print method has pointer receiver)
cannot call pointer method on m[“x”] cannot take the address of m[“x”]
53.更新 map 字段的值
如果 map 一个字段的值是 struct 类型,则无法直接更新该 struct 的单个字段:
// 无法直接更新 struct 的字段值
type data struct {name string
}func main() {m := map[string]data{"x": {"Tom"},}m["x"].name = "Jerry"
}
cannot assign to struct field m[“x”].name in map
因为 map 中的元素是不可寻址的。需区分开的是,slice 的元素可寻址:
type data struct {name string
}func main() {s := []data{{"Tom"}}s[0].name = "Jerry"fmt.Println(s) // [{Jerry}]
}
注意:不久前 gccgo 编译器可更新 map struct 元素的字段值,不过很快便修复了,官方认为是 Go1.3 的潜在特性,无需及时实现,依旧在 todo list 中。
更新 map 中 struct 元素的字段值,有 2 个方法:
- 使用局部变量
// 提取整个 struct 到局部变量中,修改字段值后再整个赋值
type data struct {name string
}func main() {m := map[string]data{"x": {"Tom"},}r := m["x"]r.name = "Jerry"m["x"] = rfmt.Println(m) // map[x:{Jerry}]
}
使用指向元素的 map 指针
func main() {m := map[string]*data{"x": {"Tom"},}m["x"].name = "Jerry" // 直接修改 m["x"] 中的字段fmt.Println(m["x"]) // &{Jerry}
}
但是要注意下边这种误用:
func main() {m := map[string]*data{"x": {"Tom"},}m["z"].name = "what???" fmt.Println(m["x"])
}
panic: runtime error: invalid memory address or nil pointer dereference
54.nil interface 和 nil interface 值
虽然 interface 看起来像指针类型,但它不是。interface 类型的变量只有在类型和值均为 nil 时才为 nil
如果你的 interface 变量的值是跟随其他变量变化的(雾),与 nil 比较相等时小心:
func main() {var data *bytevar in interface{}fmt.Println(data, data == nil) // <nil> truefmt.Println(in, in == nil) // <nil> truein = datafmt.Println(in, in == nil) // <nil> false // data 值为 nil,但 in 值不为 nil
}
如果你的函数返回值类型是 interface,更要小心这个坑:
// 错误示例
func main() {doIt := func(arg int) interface{} {var result *struct{} = nilif arg > 0 {result = &struct{}{}}return result}if res := doIt(-1); res != nil {fmt.Println("Good result: ", res) // Good result: <nil>fmt.Printf("%T\n", res) // *struct {} // res 不是 nil,它的值为 nilfmt.Printf("%v\n", res) // <nil>}
}// 正确示例
func main() {doIt := func(arg int) interface{} {var result *struct{} = nilif arg > 0 {result = &struct{}{}} else {return nil // 明确指明返回 nil}return result}if res := doIt(-1); res != nil {fmt.Println("Good result: ", res)} else {fmt.Println("Bad result: ", res) // Bad result: <nil>}
}
55.堆栈变量
你并不总是清楚你的变量是分配到了堆还是栈。
在 C++ 中使用 new 创建的变量总是分配到堆内存上的,但在 Go 中即使使用 new()、make() 来创建变量,变量为内存分配位置依旧归 Go 编译器管。
Go 编译器会根据变量的大小及其 “escape analysis” 的结果来决定变量的存储位置,故能准确返回本地变量的地址,这在 C/C++ 中是不行的。
在 go build 或 go run 时,加入 -m 参数,能准确分析程序的变量分配位置:
56.GOMAXPROCS、Concurrency(并发)and Parallelism(并行)
Go 1.4 及以下版本,程序只会使用 1 个执行上下文 / OS 线程,即任何时间都最多只有 1 个 goroutine 在执行。
Go 1.5 版本将可执行上下文的数量设置为 runtime.NumCPU() 返回的逻辑 CPU 核心数,这个数与系统实际总的 CPU 逻辑核心数是否一致,取决于你的 CPU 分配给程序的核心数,可以使用 GOMAXPROCS 环境变量或者动态的使用 runtime.GOMAXPROCS() 来调整。
误区:GOMAXPROCS 表示执行 goroutine 的 CPU 核心数,参考文档
GOMAXPROCS 的值是可以超过 CPU 的实际数量的,在 1.5 中最大为 256
func main() {fmt.Println(runtime.GOMAXPROCS(-1)) // 4fmt.Println(runtime.NumCPU()) // 4runtime.GOMAXPROCS(20)fmt.Println(runtime.GOMAXPROCS(-1)) // 20runtime.GOMAXPROCS(300)fmt.Println(runtime.GOMAXPROCS(-1)) // Go 1.9.2 // 300
}
57.读写操作的重新排序
Go 可能会重排一些操作的执行顺序,可以保证在一个 goroutine 中操作是顺序执行的,但不保证多 goroutine 的执行顺序:
var _ = runtime.GOMAXPROCS(3)var a, b intfunc u1() {a = 1b = 2
}func u2() {a = 3b = 4
}func p() {println(a)println(b)
}func main() {go u1() // 多个 goroutine 的执行顺序不定go u2() go p()time.Sleep(1 * time.Second)
}
运行效果:
如果你想保持多 goroutine 像代码中的那样顺序执行,可以使用 channel 或 sync 包中的锁机制等。
58.优先调度
你的程序可能出现一个 goroutine 在运行时阻止了其他 goroutine 的运行,比如程序中有一个不让调度器运行的 for 循环:
func main() {done := falsego func() {done = true}()for !done {}println("done !")
}
for 的循环体不必为空,但如果代码不会触发调度器执行,将出现问题。
调度器会在 GC、Go 声明、阻塞 channel、阻塞系统调用和锁操作后再执行,也会在非内联函数调用时执行:
func main() {done := falsego func() {done = true}()for !done {println("not done !") // 并不内联执行}println("done !")
}
可以添加 -m 参数来分析 for 代码块中调用的内联函数:
你也可以使用 runtime 包中的 Gosched() 来 手动启动调度器:
func main() {done := falsego func() {done = true}()for !done {runtime.Gosched()}println("done !")
}
参考转自:http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#string_byte_slice_conv
Golang可能会踩的58个坑之高级篇相关推荐
- Golang 新手可能会踩的 50 个坑
前言 Go 是一门简单有趣的编程语言,与其他语言一样,在使用时不免会遇到很多坑,不过它们大多不是 Go 本身的设计缺陷.如果你刚从其他语言转到 Go,那这篇文章里的坑多半会踩到. 如果花时间学习官方 ...
- 【Go入门可能会踩的坑-02】
Go语言是一门简单有趣的编程语言,与其他语言一样,在使用时不免会遇到很多坑,不过它们大多不是Go本身的设计缺陷.如果你刚从其他语言转到Go,那这篇文章可能会帮到你.旨在帮助你跳过这些坑,能减少大量调试 ...
- python iocp_记对协程增加IOCP支持时候踩过的一些坑
之前在对tbox的协程库中增加了基于IOCP的io处理,期间踩了不少的坑,这边就做个简单记录吧,省的到时候忘记了,自己看不懂自己这个代码 (= =) 坑点一 WSARecv/WSASend在lpNum ...
- 与webview打交道中踩过的那些坑
随着HTML5被越来越多的用到web APP的开发当中,webview这一个神器便日渐凸显出重要地位.简要的说,webview能够在移动应用中开辟出一个窗口,在里面显示html页面,css以及js代码 ...
- 那些年我们踩过的Hive坑
原文地址:https://blog.csdn.net/sunnyyoona/article/details/51648871 1. 缺少MySQL驱动包 1.1 问题描述 Caused by: org ...
- [Hive]那些年我们踩过的Hive坑
1. 缺少MySQL驱动包 1.1 问题描述 Caused by: org.datanucleus.store.rdbms.connectionpool.DatastoreDriverNotFound ...
- 安装python爬虫scrapy踩过的那些坑和编程外的思考
'转载地址:http://www.cnblogs.com/rwxwsblog/p/4557123.html' 这些天应朋友的要求抓取某个论坛帖子的信息,网上搜索了一下开源的爬虫资料,看了许多对于开源爬 ...
- Vue2.0配置mint-ui踩过的那些坑
Vue2.0配置mint-ui踩过的那些坑 最近开发项目的时候逐渐采用vue.js+mint-ui的技术栈,但是昨天开始配置开发环境的时候,遇到了各种报错,即使是按照两家的官方文档配置,也还是会报错, ...
- charles都踩过哪些坑_开水果店的你,踩过了哪些坑?
我们认为,开水果店遇到的大小问题,很多时候是有共性的.不论是开店新手还是老手,看看这里的案例,是否可以避免走一些弯路呢? 案例1 刘大飞第1次创业开水果踩过的那些坑.创业开水果店之前,刘大飞和合伙人一 ...
- 美团在Redis上踩过的一些坑-5.redis cluster遇到的一些问题
转载请注明出处哈:http://carlosfu.iteye.com/blog/2254154 由于演讲时间有限,有关Redis-Cluster,演讲者没做太多介绍,简单的介绍了一些Redis-Clu ...
最新文章
- 初探Object Pascal的类(三)
- php mysql商品管理_PHP基础示例:商品信息管理系统v1.1[转]
- 绑定变量窥测(Bind Variable Peeking)
- Ubuntu 下连接Github
- c语言中size of 用法,C语言中sizeof()的用法
- mysql查询优化explain命令详解
- python3 安装Crypto 出现的问题
- grafana授权公司内部邮箱登录 ldap配置
- linux查看内核版本信息
- Java 中文乱码问题
- 维修频谱分析仪多少钱?简单告诉你,频谱分析仪维修实例报价
- 【20210305期AI简报】基于TensorRT完成NanoDet模型部署、Google AI发布TensorFlow 3D
- execl筛选去重_excel表格如何去除重复数据进行筛选
- Spring Cloud 如何统一异常处理?写得太好了!
- A002-185-2521-李子泓
- SDY2205使用说明书
- 用adb工具给智能电视安装应用(在电视没法安装应用的情况下可用)
- (转)某618大促项目的复盘总结
- python windows 解决 cl.exe 的问题
- python,exp指数函数方法的使用,及解析