前言

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个坑之高级篇相关推荐

  1. Golang 新手可能会踩的 50 个坑

    前言 Go 是一门简单有趣的编程语言,与其他语言一样,在使用时不免会遇到很多坑,不过它们大多不是 Go 本身的设计缺陷.如果你刚从其他语言转到 Go,那这篇文章里的坑多半会踩到. 如果花时间学习官方 ...

  2. 【Go入门可能会踩的坑-02】

    Go语言是一门简单有趣的编程语言,与其他语言一样,在使用时不免会遇到很多坑,不过它们大多不是Go本身的设计缺陷.如果你刚从其他语言转到Go,那这篇文章可能会帮到你.旨在帮助你跳过这些坑,能减少大量调试 ...

  3. python iocp_记对协程增加IOCP支持时候踩过的一些坑

    之前在对tbox的协程库中增加了基于IOCP的io处理,期间踩了不少的坑,这边就做个简单记录吧,省的到时候忘记了,自己看不懂自己这个代码 (= =) 坑点一 WSARecv/WSASend在lpNum ...

  4. 与webview打交道中踩过的那些坑

    随着HTML5被越来越多的用到web APP的开发当中,webview这一个神器便日渐凸显出重要地位.简要的说,webview能够在移动应用中开辟出一个窗口,在里面显示html页面,css以及js代码 ...

  5. 那些年我们踩过的Hive坑

    原文地址:https://blog.csdn.net/sunnyyoona/article/details/51648871 1. 缺少MySQL驱动包 1.1 问题描述 Caused by: org ...

  6. [Hive]那些年我们踩过的Hive坑

    1. 缺少MySQL驱动包 1.1 问题描述 Caused by: org.datanucleus.store.rdbms.connectionpool.DatastoreDriverNotFound ...

  7. 安装python爬虫scrapy踩过的那些坑和编程外的思考

    '转载地址:http://www.cnblogs.com/rwxwsblog/p/4557123.html' 这些天应朋友的要求抓取某个论坛帖子的信息,网上搜索了一下开源的爬虫资料,看了许多对于开源爬 ...

  8. Vue2.0配置mint-ui踩过的那些坑

    Vue2.0配置mint-ui踩过的那些坑 最近开发项目的时候逐渐采用vue.js+mint-ui的技术栈,但是昨天开始配置开发环境的时候,遇到了各种报错,即使是按照两家的官方文档配置,也还是会报错, ...

  9. charles都踩过哪些坑_开水果店的你,踩过了哪些坑?

    我们认为,开水果店遇到的大小问题,很多时候是有共性的.不论是开店新手还是老手,看看这里的案例,是否可以避免走一些弯路呢? 案例1 刘大飞第1次创业开水果踩过的那些坑.创业开水果店之前,刘大飞和合伙人一 ...

  10. 美团在Redis上踩过的一些坑-5.redis cluster遇到的一些问题

    转载请注明出处哈:http://carlosfu.iteye.com/blog/2254154 由于演讲时间有限,有关Redis-Cluster,演讲者没做太多介绍,简单的介绍了一些Redis-Clu ...

最新文章

  1. 初探Object Pascal的类(三)
  2. php mysql商品管理_PHP基础示例:商品信息管理系统v1.1[转]
  3. 绑定变量窥测(Bind Variable Peeking)
  4. Ubuntu 下连接Github
  5. c语言中size of 用法,C语言中sizeof()的用法
  6. mysql查询优化explain命令详解
  7. python3 安装Crypto 出现的问题
  8. grafana授权公司内部邮箱登录 ldap配置
  9. linux查看内核版本信息
  10. Java 中文乱码问题
  11. 维修频谱分析仪多少钱?简单告诉你,频谱分析仪维修实例报价
  12. 【20210305期AI简报】基于TensorRT完成NanoDet模型部署、Google AI发布TensorFlow 3D
  13. execl筛选去重_excel表格如何去除重复数据进行筛选
  14. Spring Cloud 如何统一异常处理?写得太好了!
  15. A002-185-2521-李子泓
  16. SDY2205使用说明书
  17. 用adb工具给智能电视安装应用(在电视没法安装应用的情况下可用)
  18. (转)某618大促项目的复盘总结
  19. python windows 解决 cl.exe 的问题
  20. python,exp指数函数方法的使用,及解析

热门文章

  1. 批量获取百度网盘文件目录
  2. 鹏业安装算量软件功能按钮汇总(一)
  3. 返乡报备小程序开发制作功能介绍
  4. 团队成立——Microhard
  5. 天龙八部3d最新服务器,天龙八部3D妙笔生花新服务器开启公告
  6. Python 使用turtle在画布的随机位置绘制颜色随机的五角星
  7. python如何切换windows窗口_python3 selenium 切换窗口的几种方法小结
  8. 行业下行,丧失亮点的OPPO慢人一步
  9. 【电脑自检后无法进入电脑系统的搞定妙方】
  10. 【论文写作】投稿心路