Skip to content

Instantly share code, notes, and snippets.

@wfjsw
Last active December 28, 2020 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wfjsw/c800fe30aa6665b6baa7377fdc07c22d to your computer and use it in GitHub Desktop.
Save wfjsw/c800fe30aa6665b6baa7377fdc07c22d to your computer and use it in GitHub Desktop.
Go 坑与经验教训

Go 坑与经验教训

记录一些 Go 应用开发时可能遇到的坑以备未来参考。个人见解或许有误。

  1. 注意限制传入 make 的长度范围,尤其是基于外部输入的时候,避免内存爆炸。错误写法范例: https://github.com/mumble-voip/grumble/blob/181a4f321998046264246cfeb348803636c1c386/cmd/grumble/client.go#L246-L252
	// Read the message length (32-bit big-endian unsigned integer)
	err = binary.Read(client.reader, binary.BigEndian, &length)
	if err != nil {
		return
	}

	buf := make([]byte, length)
  1. 尽量减少 make 的使用,hot path 中可以考虑使用 sync.Pool 复用(限定最大长度)。正确写法范例: https://github.com/xtaci/kcp-go/blob/582575ef06d2207d68aeb6c8ce71605a42ace110/sess.go#L47-L58 https://github.com/xtaci/kcp-go/blob/582575ef06d2207d68aeb6c8ce71605a42ace110/sess.go#L566 https://github.com/xtaci/kcp-go/blob/582575ef06d2207d68aeb6c8ce71605a42ace110/sess.go#L338
var (
	// a system-wide packet buffer shared among sending, receiving and FEC
	// to mitigate high-frequency memory allocation for packets, bytes from xmitBuf
	// is aligned to 64bit
	xmitBuf sync.Pool
)

func init() {
	xmitBuf.New = func() interface{} {
		return make([]byte, mtuLimit)
	}
}
bts := xmitBuf.Get().([]byte)[:len(buf)]
xmitBuf.Put(s.txqueue[k].Buffers[0])
  1. 不要在包之间转移大量(变长)内存的所有权
  2. 不要一次开好多 goroutine。有条件制作几个队列,在一个固定大小的 goroutine 池中安排作业
  3. 用内置 http 库时 body.close 的 defer 不要写得太早
  4. 永远使用正常的错误包装库,如 pkg/errors
  5. 内置库很垃圾时可以看一下 golang.org/x 和 github.com/pkg 找轮子 (比如 golang.org/x/net/ipv4.PacketConn.SendBatch)
  6. channel 的 close 和 gc 之间没有屁点关系,两个之间没有直接决定关联。没有用 for range channel 就不要 close。更不要试图检查 channel 是不是关掉了,用一些 recover 处理异常以替代检查。错误写法范例: https://github.com/Mrs4s/MiraiGo/blob/7e587643012f6e8f0f1916f826de2e3057ac8ad6/client/group_msg.go#L71-L78
	ch := make(chan int32)
	c.onGroupMessageReceipt(eid, func(c *QQClient, e *groupMessageReceiptEvent) {
		if e.Rand == mr && !utils.IsChanClosed(ch) {
			ch <- e.Seq
		}
	})
	defer c.onGroupMessageReceipt(eid)
	defer close(ch)
  1. 留意一些莫名其妙的 struct 复制,尤其在给结构体起别名时
  2. 在 sync.Map 和加锁 map 之间权衡。如非不同组 key 操作一般取加锁 map。需要不停遍历所有元素时用加锁 map。
  3. 可以 atomic 就不用锁了(计数器)
  4. 文件少觉得混沌时可以试着多拆分几个文件
  5. 没事盯着 pprof 多看看
  6. 锁里边干事的时候千万小心不要又上锁(尤其是异常跳出的时候)
  7. 除非有十足把握一段代码不会 panic,否则不要不用 defer 的把 mutex 包在这段代码两边。可以使用下述奇技淫巧:
func () {
  mutex.Lock()
  defer mutex.Unlock()
  // do shit
}()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment