本文将用简洁易懂的语言,为大家提供一些在golang语言中,那些能够"饮鸩止渴"的提高代码质量的并发编程技巧。
goroutine
如果你的goroutine在另一个goroutine返回之前无法取得进展,这种场景下应该只用一个goroutine完成工作。这样能消除将结果从goroutine返回到它的启动器所需要的chan操作。下面是一个错误的示范。

如果你不知道一个goroutine什么时候结束,那就不要启动它。比如说分别用goroutine运行了两个web服务,并且你希望这两个web服务要么都运行,要么都不运行,那你就必须要知道这个goroutine的状态,并且有主动让他退出的手段。
goroutine的pianc会引发整个进程的退出,可以这样去捕捉。

启动goroutine的应该是包的调用者,所以包的设计者应该尽可能地把是否并发的控制逻辑交出去。反过来说如果调用者都不知道我调用一次就会开启多少个goroutine,这是比较危险的行为。
服务内不可恢复的错误,可以传递出一个信号,对服务进行优雅停止。

利用context包提供的方法可以轻松实现超时控制。


标准库sync包
最晚加锁,最早释放。任何语言中都要遵循这个原则,减少锁住的时间,能够充分利用硬件资源,不再赘述。
能够使用原子操作的场景,就要避免加锁,因为加锁涉及到更多的上下文切换。附上一张性能测试对比图。

读多写少的并发场景下灵活运用写时复制(copy on write)可以无锁访问共享数据。具体思路是写操作时,全量复制一个老的对象,写完之后再使用原子操作替换掉旧的数据。

使用sync.Pool 保存临时对象, 可以减少内存分配和GC的压力。
channel
一定要交给发送者来关闭channel,因为向关闭的channel发送数据会painc。
利用channel实现一个pipeline, 这种方法可以很容易的把代码安单一职责原则进行拆分。

扇入/扇出(fan-in/fan-out) 多个channel被同一个函数消费, 并汇总到一个chennel/多个函数消费同一个channel。

标准库context包
context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。

context 一定要显示传递, 不要包在结构体里。
通常要放在参数的第一个。
如果你不知道要用哪个context, 请用标准库提供的context.TODO()。
用来传递共享数据

用来取消goroutine

ctx只应该传递信号,所以不要把你的业务数据放进去。
errgroup
当我们把一个复杂任务分解为多个,并委派到不同的goroutine去执行,并把结果汇总。errgroup十分适合在这种场景下,管理goroutine。
这个包提供了完善的管理并行goroutine的方法,包括错误处理或者优雅降级,context的局部传播或者取消.

Referneces
https://coolshell.cn/articles/21228.html
https://golang.org/doc/effective_go





