哦,原来是这么回事:Golang 中的一些常识( 二 )


举个栗子 , 以下代码可能是一个bug:
func main() {var ids []intappendSlice(ids)fmt.Println("main", len(ids))}func appendSlice(ids []int) {for i := 0; i < 4; i++ {ids = append(ids, i)}fmt.Println("appendSlice", len(ids))}// 输出 ,因为appendSlice中的ids并不是main中的ids.appendSlice 4main 0其次 , Copy发生在函数调用的时候 。 比如利用这个原理就可以使用以下代码打印函数耗时 。
func do(){// 因为 defer 语句执行的时候已经将函数参数转储 , 只是函数体执行时机有所调整defer func(t time.Time) {fmt.Println("do Cost: "time.Slice(t).Second())}(time.Now())// balabalabala}3. for _, i := range ss ,ss 中的元素是 copy 到 变量i 的现象for range 的时候 slice 中的元素是copy给 变量i的 , 并且下次for循环 , 变量i会被直接覆盖 。 并不是把 n号元素的地址给了i , i 是第n 号元素的 copy 。
理解值Copy会产生两个变量 , i 是个临时变量 , 下一次for循环就会被覆写 , 而且因为是临时值 , 所以以下代码因为更改也不生效 , 也是非常常见的bug 。
type User struct {Uid int}func main() {users := []User{{Uid: 1}, {Uid: 2},}for idx, i := range users {i.Uid = 2fmt.Printf("i=%p, user_%d=%p\n",--tt-darkmode-color: #EF7060;">[]*User , 这样对于i的修改会被自动寻址到数字元素上 。 另一种是使用下标 主动寻址如 users[idx].Uid = 2。 至于[]T还是[]*T 的问题我们接下来再讨论 。
这个问题看似简单 , 如果将其使用go关键字并发将会发生巨大威力 , 造成血淋淋的事故 。
其实用go的公司经常听到这样的事故:

  • 某公司发运营push全部发给了同一个uid
  • 某研发发运营消息发短信发给了同一个uid (如果通道商不限制 , 我相信用户哭了 , 哄不好的那种)
  • 批量发优惠券 , 给同一个uid发了几百张
  • ....
闭包问题一点都不新鲜 , 就是由于在go func里边使用for了循环的变量i了 , 然后因为函数体并没在go的时候立即执行需要申请资源挂载然后由M进行运行需要一些时间 , 所以一般for循环执行一段时间之后go func才会执行 , 这时候 内部函数取到的值就得听天命了 。
经典bug复现
func main() {for _, i := range []int{1, 2, 3} {go func() {println(i)}()}time.Sleep(1* time.Millisecond)}// 只会打印 3 ,因为等到func执行的时候 i已经变成3了// 所以把 i 当做 匿名函数的参数传进去或者在for中重新定义一个变量是个不错的做法333所以 , 使用匿名函数的时候go func的时候要时刻注意循环变量的Scope, 该传参传参 , 该重新定义重新定义 。 好在 Goland 最新版本已经会提示i存在Scope问题了 。 但是好像没几个人会注意IDE警告 , 所以 , 习惯很重要 , 不要写出IDE警告的代码也是一个不错的编程理念 。
4. []T 还是 []*T现象一般来说[]T 会比较高效一些 , 但是如果T比较大 , 在For循环时存在Copy开销 , 个人觉得[]*T也是可以的 。
5. []interface{}并不能接收[]T类型现象很多时候我们都以为interface可以传递任意类型 , 凡事总有例外 , 他就不能接收 []T 类型, 如果你需要进行赋值 , 那你要将T转成interface{}
理解因为一个[]interface{}的空间是一定的 , 但是 []T 不是 , 因为占用空间不一致 , 编译器觉得有些代价 , 并没有进行转换.
6. Send on closed chan 会Panic , 但是 Receive from closed chan 不会现象往已经关闭的channel 再send数据会触发runtime panic , 但是receive从已经关闭的channel中消费不会触发.
理解很多人有误区 , 认为chan关闭了就不能再操作了 , 但是send进chan的数据总归要消费完的 , 不然就丢了 , 你品 。


推荐阅读