Golang常见的坑和编程模式

2020-02-27 275浏览

  • 1.Golang 中常见的坑与编码模 式 刘奇 新浪微博 @goroutine
  • 2.与 go 一起的那些年 • 2009 相识 ( 只是因为在人群中多看了 go 一眼,再也没能忘掉 go 的容颜 ) • 2010-2011 相知 ( 红 红 之 尘中呢? ) • 2012 相爱 ( 带刺的玫瑰 ) 中知己 红红红 红 求,沙
  • 3.惊红 一瞥 红红
  • 4.为何偏偏爱上你 • • • • • 良好的并发支持 静态链接 简洁 直观 语言级的并发与自动化垃圾回收支持 卓越的跨平台支持
  • 5.Go 语言的目标 ( 理想是美好 的) • 同时具备静态语言的运行效率、动态语言 的开发效率 • 类型安全、内存安全 • 优秀的并发与通信能力 • 高效、无延红红 的自 红 红红 化垃圾收集 红 红 红红 • 高速编译与静态链接 • 丰富、高质量的包
  • 6.现实是凑合的 • Go 是一门很二,很二的语言 ( 是表扬,大 家 hold 住鸡蛋和西红柿 ) • 成熟度不如 erlang • 速度不如 c ,目前和 java 还有一点差距 • 红比 不上java , python, c, cpp, nodejs • 红红不 如 ruby, python
  • 7.未来是美好的 • Golang 正在快速红红 步 • 库越来越丰富 • 性能越来越高
  • 8.Go 的国学思维 ( 中庸 ) • 几乎每个方面都是第二阵营,所以玩铁人 三项才能拿冠军 ( 又一个小米手机? ) • 所以成了集大成者
  • 9.那些年,那些坑 • 总觉着踩到坑里了才算是真爱过红 • 有人说踩到的坑越多,爱得越深 • 也有人说情越深,分得越快
  • 10.List 的遍历删除 • 错误写法 ( 简洁,漂亮,但是。。。 ) : • for e := l.Front(); e != nil; e = e.Next() { • l.Remove(e) • } • • • • • • 正确写法 var next *Element for e := l.Front(); e != nil; e = next { next = e.Next() l.Remove(e) }
  • 11.查找原因 • // 看看具体实现代码 (container/list.go) • func (l *List) remove(e *Element) *Element { • e.prev.next = e.next • e.next.prev = e.prev • e.next = nil // avoid memory leaks • e.prev = nil // avoid memory leaks • e.list = nil • l.len-• return e • }
  • 12.Time Formatting / Parsing • t := time.Now() • fmt.Println(t.Format("2006-01-02T15:04:05Z07:00")) • 亲,记住了,只能用 2006 01 02 15 0 4 ,别问我为什么,一个如此讲究用户体验 的好语言出现这个约束确实有点难以理解
  • 13.查找原因 • • • • • • • • • • 这里写死了 (time/format.go) const ( ANSIC = "Mon Jan _2 15:04:05 2006" UnixDate = "Mon Jan _2 15:04:05 MST 2006" RubyDate = "Mon Jan 02 15:04:05 -0700 2006" RFC822 = "02 Jan 06 15:04 MST" RFC3339 = "2006-01-02T15:04:05Z07:00" …… ) 详见 nextStdChunk 函数
  • 14.range • • • • • • • values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) }() } 输出三个 c ,而不是顺序的 a, b, c
  • 15.查找原因 • • • • • • • • values := []string{"a", "b", "c"} for _, v := range values { //(1) go func() { //(2) fmt.Println(v) // 隐式用 v 的地址传递 }() } (1) 复用了临时变量,只有一个临时变量的空间 (2) 只是 goroutine 放到调度队列,不是立刻运行, 还要排队先,等排到自己的时候,黄花菜已经凉了
  • 16.解决方案 • for _, v := range values { • go func(u string) { • fmt.Println(u) }(v) // 明确值复制,作为栈变量 • }
  • 17.类似的 • list := make(map[int]*Link) for _, lnk := range linktree { list[lnk.Code] = &lnk }
  • 18.解决方案 • list := make(map[int]*Link) for _, lnk := range linktree { var lnk = linktree list[lnk.Code] = &lnk }
  • 19.Build once, run everywhere? • Build on ubuntu 12.04 (golang tip ver sion) • Run on centos 5.6 may panic ! • 未测试 Golang 1.1 beta ,目前还不稳定 ,建议暂时不在生产环境使用
  • 20.原因 • Syscall not match?
  • 21.解决方案 • Update centos kernel • Or build on centos 5.6
  • 22.Channel 的唤醒时序 • 当多个 channel 都处于就绪状态时,激活 的 channel 是随机的 • A Tour ofGo:A select blocks until o ne of its cases can run, then it execut es that case. It chooses one at rando m if multiple are ready • 切勿想当然的认为先来后到
  • 23.queue • Golang 没有直接的 queue • 可以预期 queue 长度的情况下用 channel • 无法预期长度是用 list 当作队列用 • 也可以根据实际情况,将 list 作为 channe l 的二级队列 (sometimes you may nee d it)
  • 24.解决方案 • 需要严格顺序处理的都放入一个 channel • 设计系统时需要仔细考虑时序的影响,特 别是并发环境下
  • 25.让人又爱又恨的 GC • 爱她的轻柔和美丽:代码看起来干净,整 洁 • 恨她关键时刻停下来了: pause on mark ing and sweeping ,比如网游和人 pk 的 时候,或者实时系统,试想如果汽车刹车 的时候刚好触发 gc ,后果很严重
  • 26.原因 • 原始的标记清扫算法,一次完成整个标记 清扫过程,没有分代 ( 分生存期 ) 。想想每 天洗一件衣服和一周洗一次衣服的差别 • 程序产生大量的临时垃圾,引用关系复杂
  • 27.解决方案 • Golang 1.1 :已经支持并行 gc ,有较大 改善 • 使用 c 模红 来管理内存, 红 红 红 红 红 红 c.malloc 和 c.fr ee • Object pool, buffer pool
  • 28.Golang 编码模式
  • 29.惊艳的 defer • func FileOp() { • f := os.Open(file) • defer f.Close() • // write data ... • } • 常用来做资源清理、关闭文件、解锁、记 录执行时间等
  • 30.Defer:来自标准库 io.pipe 的例 子 • func (p *pipe) read(b []byte) (n int, err error) { • // One reader at a time. • p.rl.Lock() • defer p.rl.Unlock() • p.l.Lock() • defer p.l.Unlock() • …… • }
  • 31.Defer:来自标准库的例子 sql.go 连接池资源管理 • • • • • • • • ci, err := db.conn() if err != nil { return nil, err } defer func() { // 简洁,优雅 db.putConn(ci, err) }() ……
  • 32.Defer:一句话给函数计时 • func f() { • defer timeoutCheck(“xx slow”, time.Now()) • } • func timeoutCheck(tag string, start time.Time) { • dis := time.Since(start).Seconds() • if dis > 1 { • log.Println(tag, dis, "s") } • }
  • 33.Function design • trying to return error if you can • func (ns NullString) Value() (driver.Val ue, error) { • if !ns.Valid { • return nil, nil • } • return ns.String, nil • }
  • 34.让 import 更美观 • • • • • • • • • import ( "encoding/json" "fmt" "net" ) 而不是 import "encoding/json" import "fmt" import "net"
  • 35.避免大对象的拷贝 • 如果 map 的 value 较大,通常应该使用指针 来存红 ,以避免性能 红 红红 红红红 红红红 ,红 红 似的 红红 红 有 ch annel , slice 等 • 避免 []byte 和 string 的反复来回转换
  • 36.About panic
  • 37.About panic • Panic if and only if you can’t handle it
  • 38.Work pool • func worker(jobs <-chan int, results chan<- int) { • for j := range jobs { • results <- xx • } • } • for w := 1; w <= 100; w++ { • go worker(w, jobs, results) • }
  • 39.Object pool & buffer pool
  • 40.一个简单的 Connection pool • • • • • • • • • • • • • • • func (p *ConnectionPool) InitPool(size int, f FactoryMethod) { p.conn = make(chan interface{}, size) for i := 0; i < size; i++ { p.conn <- f() } p.size = size } func (p *ConnectionPool) Get() interface{} { return <-p.conn } func (p *ConnectionPool) Put(conn interface{}) { p.conn <- conn }
  • 41.Connection pool • 一个更加完善的例子https://code.google.com/p/vitess/source/browse/go/pools/roundrobin.go • 来自 youtube 的开源项目 vtocc
  • 42.goroutine • Goroutine is cheap , not free( 每个 goro utine 约占用 6k 的内存,红红红 代红红 很容易达到 红 红 红15-3 红 0K) • 设计系统时要能预见并控制 goroutine 的总数 ,必要时可以发放 ticket 来控制资源 • 避免滥用 goroutine ,不合理的并行会带来更 多的 bug ,也让问题难以复现和调试
  • 43.及时,批处理模式 • • • • • • • • • • • • • • • • • • arr := make([]int, 8192) for { select { casex:=<-ch:'>ch: