Golang58个坑高级篇5258
52.使用指针作为方法的 receiver 53.更新 map 字段的值 54.nil interface 和 nil interface 值 55.堆栈变量 56.GOMAXPROCS、Concurrency(并发)and Parallelism(并行) 57.读写操作的重新排序 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"] = r fmt.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 *byte var in interface{} fmt.Println(data, data == nil) // true fmt.Println(in, in == nil) // true in = data fmt.Println(in, in == nil) // false // data 值为 nil,但 in 值不为 nil }
如果你的函数返回值类型是 interface,更要小心这个坑: // 错误示例 func main() { doIt := func(arg int) interface{} { var result *struct{} = nil if arg > 0 { result = &struct{}{} } return result } if res := doIt(-1); res != nil { fmt.Println("Good result: ", res) // Good result: fmt.Printf("%T ", res) // *struct {} // res 不是 nil,它的值为 nil fmt.Printf("%v ", res) // } } // 正确示例 func main() { doIt := func(arg int) interface{} { var result *struct{} = nil if 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: } }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)) // 4 fmt.Println(runtime.NumCPU()) // 4 runtime.GOMAXPROCS(20) fmt.Println(runtime.GOMAXPROCS(-1)) // 20 runtime.GOMAXPROCS(300) fmt.Println(runtime.GOMAXPROCS(-1)) // Go 1.9.2 // 300 }57.读写操作的重新排序
Go 可能会重排一些操作的执行顺序,可以保证在一个 goroutine 中操作是顺序执行的,但不保证多 goroutine 的执行顺序: var _ = runtime.GOMAXPROCS(3) var a, b int func u1() { a = 1 b = 2 } func u2() { a = 3 b = 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 := false go func() { done = true }() for !done { } println("done !") }
for 的循环体不必为空,但如果代码不会触发调度器执行,将出现问题。
调度器会在 GC、Go 声明、阻塞 channel、阻塞系统调用和锁操作后再执行,也会在非内联函数调用时执行: func main() { done := false go func() { done = true }() for !done { println("not done !") // 并不内联执行 } println("done !") }
可以添加 -m 参数来分析 for 代码块中调用的内联函数:
你也可以使用 runtime 包中的 Gosched() 来 手动启动调度器: func main() { done := false go func() { done = true }() for !done { runtime.Gosched() } println("done !") }
浅谈释放压力最好的办法面对日益严峻的职场压力,亲朋关系,我们难免有情绪不好的时候,这个时候我们应该以什么样的心态和方式来面对呢?按照传统的看法,一个人在人前哭泣流泪,就意味着他将自己的缺陷和柔弱暴露在人
权力关进制度的笼子和透明公开是对人民最好的交代权力的游戏3。这个世界上拥有最大力量的是国家,拥有最大权力的也是国家。国家由政府和人民组成。政府是领头羊,是先锋队。人民是基础,是生产力的代表,是最具创新创造的保障。如果说权力是人
楹联专辑黄河三角洲诗词周刊(2023第4辑)编者按天地风霜尽,乾坤气象和。历添新岁月,春满旧山河(明叶颙)。欣值癸卯年春节来临之际,滨州市诗词学会楹联专业委员会积极响应和落实党的二十大报告中关于传承中华优秀传统文化,不断提升
安全又有趣!禅城首个儿童友好示范街今亮相1月18日,佛山市禅城区首个儿童友好示范街正式开街。该示范街坐落在禅城区石湾镇街道的深华路段,设有护学长廊智慧潮汐车道一米标识等适儿化公共设施,打造形成15分钟安学护学圈,全方位保
暖心!2023年春风行动一瞥文丨中国劳动保障报记者赵泽众2023年1月,一年一度的春风行动如约而至。一路春风相伴,就业返岗无忧。2023年的春风行动,尊重民意关注民情致力民生。各地人社部门迅速组织密集招聘活动
2006年,20岁广东女子嫁北大精英,婚后连生7胎,扬言不生浪费2006年,广东20岁打工妹张荣荣在嫁给北大毕业的丈夫后,接连为他生下七个孩子,引发网友议论。她表示丈夫智商高达140,不想浪费了丈夫的好基因,如果可以,自己以后没准还会生。一个生
敢在湖南大家谈(三)敢在湖南大家谈(一)敢在湖南大家谈(二)编者按2022年12月6日,中共中央政治局召开会议,强调要激发全社会干事创业活力,让干部敢为地方敢闯企业敢干群众敢首创。敢字蕴含的精神品质,
新年上UC,来打造一款符合你习惯的专属浏览器在日常上网的过程中,浏览器应该是必不可缺的软件之一。就拿小编来说,通勤路上会看些新闻或小说来打发时间,晚上到家刷刷视频,都是通过浏览器来进行的。而市面上浏览器众多,在经过对比后,小
清华和北大的区别?本人也是经过了深思熟虑,在每个日日夜夜思考这个问题。博曾经提到过,一次失败,只是证明我们成功的决心还够坚强。维这句话看似简单,但其中的阴郁不禁让人深思。问题的关键究竟为何?迈克尔F
我的2022,有点难过我的生活也是头条2022年,是疫情最严重的一年,对世界人民给予重创与考验。一大部分人因感染新冠生病了,有的阳康了,有的则留下了严重的后遗症,有的甚至因此永远离开人世。同时,因疫情原
人最大的恶,是见不得别人好鲁迅先生曾在文章中写到很多人都见不得别人过好日子,自己没的,若别人有自己就会心生恨,若别人正极力追求一个自己没有却也十分想要的东西便会极力撺掇说此物不好。嫉妒是人之常情,或多或少都