本文为 工作用Go: 异步任务怎么写 系列的第2篇
上面的例子, 常见有 3 种解决方案:
- 方案1: 等子协程执行完
funcTestTask(t*testing.T) { gotask() time.Sleep(time.Second) // 等待子协程执行完log.Print("req done") } functask() { // 模拟耗时任务time.Sleep(time.Second) log.Print("task done") }
- 方案2: 使用
WaitGroup
funcTestTask(t*testing.T) { varwgsync.WaitGroupwg.Add(1) gofunc() { task() wg.Done() }() wg.Wait() log.Print("req done") } functask() { // 模拟耗时任务time.Sleep(time.Second) log.Print("task done") }
WaitGroup
其实很好理解, 就是同时等待一组任务完成, 它分为 3 步: 1. Add
: 总共有多少任务; 2. Done()
: 表示任务执行完; 3. Wait()
: 等待所有任务完成
- 方案3: 使用 Go 的并发语言
chan
funcTestTask(t*testing.T) { ch :=make(chanstruct{}) // 初始化 changofunc() { task() ch<-struct{}{} // 发送到 chan }() <-ch// 从 chan 获取log.Print("req done") } functask() { // 模拟耗时任务time.Sleep(time.Second) log.Print("task done") }
Go基础知识: 通过
chan T
就可以申明T
类型的 chan, 供协程间进行通信;struct{}
是 Go 中0 memory use
(0内存占用)类型, 适合上面使用 chan 进行 控制 而不需要 数据 进行通信的情况
虽然只是3个简单的 demo code, Go 提供的 2 种并发能力都有展示:
- 传统并发原语: 大部分集中在
sync
包下, 上面案例2中的sync.WaitGroup
就是其中之一 - Go 基于 CSP 的并发编程范式: 包括
go chan select
, 上面的案例3中展示了go+chan
的基本用法
简单 Go 并发讲完了, 那任务编排又是啥? 其实, 某等程度上, 任务编排=异步
, 任务需要 分工 完成时, 也就是一个任务相对于另一个任务需要 异步处理. 而任务编排, 恰恰是 Go 语言中基于 chan 进行并发编程的强项.
Go 中有一个大的方向,就是任务编排用 Channel,共享资源保护用传统并发原语。
回到最初的代码, 在实际使用中, 到底使用的是哪种方案呢? 答案是 方案1. 看看接近真实场景的代码
funcTestTrace(t*testing.T) { for { // 服务以 daemon 的方式持续运行// 不断处理用户的请求 { gotask() log.Print("req done") } } } functask() { // 模拟耗时任务time.Sleep(time.Second) log.Print("task done") }
也就是真实场景下, 主协程所在的 server 会一直常驻, 请求(request)所有的子协程不用担心还没执行完就被强制退出了.