处理异步数据流的通用模式

处理异步数据流的通用模式

当使用通道来串行化任务时,异步流通常就诞生了。

异步编程很容易搞砸,尤其需求丰富,健壮度高时,比如拥有超时取消等。 一个经典的例子,是从文件逐行读取,交给另一个异步过程解析。通常I/O慢而计算快,为了最大化效率,一般会并行读取,放入缓存,再顺序处理,此时就需要管道来协调;如遇到读取错误,超时等,需终止整个流程。

我找到了一个通用模式,其内在逻辑很容易理解:

异步慢过程 后跟 同步快过程。

如果慢过程是 provider,则模式是:

1go provider()
2consumer()

一个完整的极简框架:

 1// 异步生产端
 2go func() {
 3    defer close(ch)
 4
 5    for {
 6        v, err := provide(ctx)
 7        if err != nil {
 8            return
 9        }
10
11        select {
12        case ch <- v:
13        case <-ctx.Done():
14            return
15        }
16    }
17}()
18
19// 阻塞的消费端
20consume:
21for {
22    select {
23    // 收到结束信号时打断
24    case <-ctx.Done():
25        err = ctx.Err()
26        break consume
27    // 收到消费数据时
28    case v, ok := <-ch:
29        if !ok {
30            break consume
31        }
32
33        // 处理失败时打断
34        if err = process(v); err != nil {
35            break consume
36        }
37    }
38}
39
40// 消费终止后,主动发出结束信号,防止生产端 goroutine 泄露
41cancel()
42
43// 后续逻辑
44if err != nil {
45    log.Error(err)
46}

如果是 provider 的过程快而 consumer 过程慢呢?比如先通过计算,快速生成消息,再缓慢的写入到文件,则需要颠倒启动的顺序:

1go consumer()
2provider()

先启动的 consumer 始终在监听 provider 的通道获取消息,provider 则是一个阻塞的同步过程,生成消息。

祝我们在异步编程中好运!