一文带你更方便的控制 goroutine

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一篇我们讲了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的并发工具包 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"core/syncx","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从整体分析来看,并发组件主要通过 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"channel + mutex","attrs":{}}],"attrs":{}},{"type":"text","text":" 控制程序中协程之间沟通。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Do not communicate by sharing memory; instead, share memory by communicating.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不要通过共享内存来通信,而应通过通信来共享内存。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本篇来聊 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 对 Go 中 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"goroutine","attrs":{}}],"attrs":{}},{"type":"text","text":" 支持的并发组件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们回顾一下,go原生支持的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"goroutine","attrs":{}}],"attrs":{}},{"type":"text","text":" 控制的工具有哪些?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"go func()","attrs":{}}],"attrs":{}},{"type":"text","text":" 开启一个协程","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"sync.WaitGroup","attrs":{}}],"attrs":{}},{"type":"text","text":" 控制多个协程任务编排","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"sync.Cond","attrs":{}}],"attrs":{}},{"type":"text","text":" 协程唤醒或者是协程等待","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那可能会问 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 为什么还要拿出来讲这些?回到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 的设计理念:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"工具大于约定和文档","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么就来看看,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 提供哪些工具?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"threading","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go func()","attrs":{}}],"attrs":{}},{"type":"text","text":" 已经很方便,但是有几个问题:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果协程异常退出,无法追踪异常栈","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"某个异常请求触发panic,应该做故障隔离,而不是整个进程退出,容易被攻击","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们看看 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"core/threading","attrs":{}}],"attrs":{}},{"type":"text","text":" 包提供了哪些额外选择:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func GoSafe(fn func()) {\n go RunSafe(fn)\n}\n\nfunc RunSafe(fn func()) {\n defer rescue.Recover()\n fn()\n}\n\nfunc Recover(cleanups ...func()) {\n for _, cleanup := range cleanups {\n cleanup()\n }\n\n if p := recover(); p != nil {\n logx.ErrorStack(p)\n }\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"GoSafe","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"threading.GoSafe()","attrs":{}}],"attrs":{}},{"type":"text","text":" 就帮你解决了这个问题。开发者可以将自己在协程中需要完成逻辑,以闭包的方式传入,由 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"GoSafe()","attrs":{}}],"attrs":{}},{"type":"text","text":" 内部 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go func()","attrs":{}}],"attrs":{}},{"type":"text","text":";","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当开发者的函数出现异常退出时,会在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Recover()","attrs":{}}],"attrs":{}},{"type":"text","text":" 中打印异常栈,以便让开发者更快确定异常发生点和调用栈。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"NewWorkerGroup","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们再看第二个:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"WaitGroup","attrs":{}}],"attrs":{}},{"type":"text","text":"。日常开发,其实 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"WaitGroup","attrs":{}}],"attrs":{}},{"type":"text","text":" 没什么好说的,你需要 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"N","attrs":{}}],"attrs":{}},{"type":"text","text":" 个协程协作 :","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"wg.Add(N)","attrs":{}}],"attrs":{}},{"type":"text","text":" ,等待全部协程完成任务:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"wg.Wait()","attrs":{}}],"attrs":{}},{"type":"text","text":",同时完成一个任务需要手动 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"wg.Done()","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看的出来,在任务开始 -> 结束 -> 等待,整个过程需要开发者关注任务的状态然后手动修改状态。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"NewWorkerGroup","attrs":{}}],"attrs":{}},{"type":"text","text":" 就帮开发者减轻了负担,开发者只需要关注:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"任务逻辑【函数】","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"任务数【","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"workers","attrs":{}}],"attrs":{}},{"type":"text","text":"】","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后启动 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"WorkerGroup.Start()","attrs":{}}],"attrs":{}},{"type":"text","text":",对应任务数就会启动:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (wg WorkerGroup) Start() {\n // 包装了sync.WaitGroup\n group := NewRoutineGroup()\n for i := 0; i < wg.workers; i++ {\n // 内部维护了 wg.Add(1) wg.Done()\n // 同时也是 goroutine 安全模式下进行的\n group.RunSafe(wg.job)\n }\n group.Wait()\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"worker","attrs":{}}],"attrs":{}},{"type":"text","text":" 的状态会自动管理,可以用来固定数量的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"worker","attrs":{}}],"attrs":{}},{"type":"text","text":" 来处理消息队列的任务,用法如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func main() {\n group := NewWorkerGroup(func() {\n // process tasks\n }, runtime.NumCPU())\n group.Start()\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Pool","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Pool","attrs":{}}],"attrs":{}},{"type":"text","text":" 不是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sync.Pool","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sync.Pool","attrs":{}}],"attrs":{}},{"type":"text","text":" 有个不方便的地方是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"它池化的对象可能会被垃圾回收掉","attrs":{}},{"type":"text","text":",这个就让开发者疑惑了,不知道自己创建并存入的对象什么时候就没了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pool","attrs":{}}],"attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"pool","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的对象会根据使用时间做懒销毁;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cond","attrs":{}}],"attrs":{}},{"type":"text","text":" 做对象消费和生产的通知以及阻塞;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"开发者可以自定义自己的生产函数,销毁函数;","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那我来看看生产对象,和消费对象在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pool","attrs":{}}],"attrs":{}},{"type":"text","text":" 中时怎么实现的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (p *Pool) Get() interface{} {\n // 调用 cond.Wait 时必须要持有c.L的锁\n p.lock.Lock()\n defer p.lock.Unlock()\n\n for {\n // 1. pool中对象池是一个用链表连接的nodelist\n if p.head != nil {\n head := p.head\n p.head = head.next\n // 1.1 如果当前节点:当前时间 >= 上次使用时间+对象最大存活时间\n if p.maxAge > 0 && head.lastUsed+p.maxAge < timex.Now() {\n p.created--\n // 说明当前节点已经过期了 -> 销毁节点对应的对象,然后继续寻找下一个节点\n // 【⚠️:不是销毁节点,而是销毁节点对应的对象】\n p.destroy(head.item)\n continue\n } else {\n return head.item\n }\n }\n // 2. 对象池是懒加载的,get的时候才去创建对象链表\n if p.created < p.limit {\n p.created++\n // 由开发者自己传入:生产函数\n return p.create()\n }\n \n p.cond.Wait()\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (p *Pool) Put(x interface{}) {\n if x == nil {\n return\n }\n // 互斥访问 pool 中nodelist\n p.lock.Lock()\n defer p.lock.Unlock()\n\n p.head = &node{\n item: x,\n next: p.head,\n lastUsed: timex.Now(),\n }\n // 放入head,通知其他正在get的协程【极为关键】\n p.cond.Signal()\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述就是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 对 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Cond","attrs":{}}],"attrs":{}},{"type":"text","text":" 的使用。可以类比 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"生产者-消费者模型","attrs":{}},{"type":"text","text":",只是在这里没有使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"channel","attrs":{}}],"attrs":{}},{"type":"text","text":" 做通信,而是用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Cond","attrs":{}}],"attrs":{}},{"type":"text","text":" 。这里有几个特性:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Cond和一个Locker关联,可以利用这个Locker对相关的依赖条件更改提供保护。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Cond可以同时支持 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Signal","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Broadcast","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,而 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Channel","attrs":{}}],"attrs":{}},{"type":"text","text":" 只能同时支持其中一种。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"总结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"工具大于约定和文档","attrs":{}},{"type":"text","text":",一直是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 设计主旨之一;也同时将平时业务沉淀到组件中,这才是框架和组件的意义。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 更多的设计和实现文章,可以持续关注我们。欢迎大家去关注和使用。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"项目地址","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/tal-tech/go-zero","title":"","type":null},"content":[{"type":"text","text":"https://github.com/tal-tech/go-zero","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"欢迎使用 go-zero 并 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"star","attrs":{}},{"type":"text","text":" 支持我们!","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"微信交流群","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关注『微服务实践』公众号并回复 进群 获取社区群二维码。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章