来自NSQ
nsq的官方文档的Dsign中提到一个PPThttps://speakerdeck.com/snakes/nsq-nyc-golang-meetup, 里面有这样一段话
总结一下.
- don’t be afraid of sync package
sync包里有
- sync.Mutex(互斥锁,一读一写)
- sync.RWMutex(读写锁,可以多读一写)
- sync.Pool(对象池, 合理利用可以减少内存分配, 降低GC压力, 稍后写一篇博客说说)
- sync.Once(并发控制. 适用于开几个goroutines去执行一个只执行一次的任务, 比如单例模式)
- sync.Cond(并发控制, cond.Wait()阻塞至其他goroutie运行到cond.Signal())
- sync.WaitGroup(并发控制. 通常用法 wg.Add增加任务数量 goroutie完成任务后执行wg.Done,任务数量减1 wg.Wait等待wg任务数量为0)
- goroutines are cheap not free
这句话在其他地方也看过,go func()
简单好用, 创建开销也很小, 但也是有开销的. 很多情况下开固定数量worker, 用channel传递数据, 效果会更好.
go-apns2中的example是个非常好的例子.https://github.com/sideshow/apns2/blob/master/_example/channel/main.go
注意一个问题, go里面一个goroutine panic了, 会导致进程退出, 所以go func()
时第一行带上
go func(){
defer func(){
if err:=recover(); err!=nil{
}
}()
}()
是安全的做法, worker channel法时类似
package main
import (
"fmt"
"log"
"time"
)
func main() {
ch := make(chan int, 10)
for i := 0; i < 2; i++ {
go worker(ch, i)
}
for i := 0; i < 3; i++ {
ch <- i
ch <- -1
}
time.Sleep(time.Second * 5)
}
func worker(ch <-chan int, goId int) {
defer func(ch <-chan int) {
if err := recover(); err != nil {
log.Printf("worker%d recover error:%s", goId, err)
go worker(ch, goId)
}
}(ch)
log.Printf("worker%d running", goId)
for data := range ch {
log.Printf("worker%d received data:%d", goId, data)
if data == -1 {
panic(fmt.Errorf("worker%d panic", goId))
}
}
}
fasthttp之所以快, 其中一个原因就是net/http是来一个连接就创建一个goroutie, 而fasthttp用了池复用了goroutines.
watch your allocations (string() is costly, re-user buffers)
go里面 []byte和string互转是会发生复制的, 开销明显, 如果代码里频繁互转, 考虑使用bytes.buffer 和 sync.Pooluse anonymous structs for arbitrary JSON
在写http api时, parse body这种事情, 如果只是纯粹取body里的json数据, 没必要单独定义结构体, 在函数里定义一个匿名结构体就好.var s struct { A int}
no built-in per-request HTTP timeouts
这是说要注意默认的httpClient没有超时synchronizing goroutine exit is hard - log each cleanup step in long-running goroutines
同步化的goroutine一不小心就没有退出, 如果你写一个长期运行的服务, 用logger记录每一个goroutine的清理退出, 防止goroutine泄露select skips nil channels
select语句是会跳过nil的channels的. 因为在Go里往已经close掉的channel里发送数据是会panic的, 可以利用select语句.
附: channel操作导致panic的情况有: 关闭一个nil的channel, 关闭一个已经关闭的channel( j,ok:= <- ch, ok为false时代表ch已经关闭了), 往一个已经关闭的channel里发送数据(从已经关闭的channel里读数据是OK的, 如果这个channel是带缓冲的, 那么可以读到所有数据)
来自GO箴言
Python有import this
的zen of Python, 想不到Go也有箴言
https://speakerdeck.com/ajstarks/go-proverbs
- 在go里, goroutines之间通信不要用共享内存的方式实现, 应该用channel来实现
- 并发不是并行
- channel是编排, mutexs是串行
- interface定义越多的方法, 抽象程度越低. Go提倡用接口组合的方式实现更大的接口
- 零值, 猜测这里说的是struct{}吧, struct{}是一个不占内存的空结构体, 在用map实现set, channel发送无额外意义的signal时能降低内存分配
- 提倡gofmt
- 一点点复制比一点点依赖好. 官方包里有时能见到一些复制的代码, 这是为了不互相依赖
- syscall每个平台实现不一样, 要加build tags
- cgo每个平台的lib不一样, 要加build tags
- Cgo不是go
- unsafe包不提供保障
- 简洁胜过高效
- error是值 可以用值的方式去处理错误: 传递, 比较
- 不用仅检查错误, 要优雅地处理
- 多花精力设计架构, 模块命名, 写详细的文档
- 写良好的文档给用户
- 对于普通错误, 应该用多值返回错误, 而不是手动panic
未知来源
- 写可重复使用的函数, 接收接口类型, 返回具体类型