Go 并发模式:超时则继续 (Go Blog 翻译)

原文地址:Go Concurrency Patterns: Timing out, moving on

并发编程有自己的习语。超时是一个很好的例子。虽然 Go channel 不直接支持超时,但它很容易实现。假设我们想从 channel ch 中获取数据,但是最多只能等待一秒钟。那么我们应该在开始的时候创建一个传递信号的 channel,并在发送数据之前启动一个 goroutine:

timeout := make(chan bool, 1)
go func() {
    time.Sleep(1 * time.Second)
    timeout <- true
}()

然后我们可以使用 select 语句去从 ch 或 timeout 中接收数据。如果一秒内没有从 ch 中读取任何数据 ,那么 timeout 会被选中,而从 ch 尝试读取数据的操作会被丢弃掉:

select {
case <-ch:
    // 从 ch 的读操作发生
case <-timeout:
    // 从 ch 的读操作超时
}

timeout channel 使用了空间大小为 1 的缓冲,所以 timeout goroutine 可以发送消息然后退出。这个 goroutine 不知道 (也不关心) 它发送的数据是否被读取。这意味着即使 ch 读到数据的时间要早于超时,超时 goroutine 也不会被阻塞住。timeout channel 最终会被垃圾回收期回收掉。

(在这个例子里,我们使用 time.Sleep 来说明 goroutine 和 channel 的机制。在实际代码中,你可以使用 time.After 函数。这个函数返回一个 channel,并在确定时间后通过这个 channel 发送消息。)

让我们看下这个模式的另一个变种。在这个例子中,我们的程序从多个复制的数据库中同步读取数据。程序只需要其中一个结果,所以它应该接收返回的第一个结果。

函数 Query 接受存放一系列数据库链接的切片以及一个请求字符串。它并行请求每个数据库,并返回它获取到的第一个结果:

func Query(conns []Conn, query string) Result {
    ch := make(chan Result)
    for _, conn := range conns {
        go func(c Conn) {
            select {
            case ch <- c.DoQuery(query):
            default:
            }
        }(conn)
    }
    return <-ch
}

例子里的闭包执行了一个非阻塞发送。它是通过在 select 语句中使用 default case 来执行发送操作来实现的。如果正常发送操作无法立即执行,那么 default 部分会被选择执行。这样就保证了在 for 循环中启动的 goroutine 不会被挂起。不过如果结果在主函数进入接收 (return <-ch) 之前到达,那么发送操作会失败,因为接收端并没有做好准备。

这个问题是一个教科书例子,被称为竞态条件。我们这里可以很容易去解决它。我们可以把 channel ch 改为有缓存 channel,确保第一次 channel 写入操作能执行成功,这样也能保证第一次获取的结果可以被读取并返回。

这两个例子说明了 Go 如何简单地在 goroutine 间进行复杂交互的操作。

-- 作者:Andrew Gerrand

转载于:https://my.oschina.net/dokia/blog/1842446

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值