Go 并发模式:超时,执行下一个

未匹配的标注

本文为官方 Go Blog 的中文翻译,详见 翻译说明

Andrew Gerrand
2010 年 9 月 23 日

并发编程有其自身的套路用法。 超时是一个很好的示例。 虽然 Go 的信道没有直接支持超时, 但非常容易实现。 假设我们要从信道 ch 接收, 但是等待最多一秒的时间该值才能到达。 首先我们创建一个信道并启动一个协程, 该示例会先休眠1秒再发送数据给通道:

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

然后我们可以使用 select 语句从 chtimeout 接收。 如果一秒后什么都没有到达 ch, 则定位超时情况并放弃从 ch 中读取。

select {
case <-ch:
    // 发生了对 ch 的读取
case <-timeout:
    // 从 ch 中读取已超时
}

timeout 信道用 1 个值的空间缓冲, 允许超时协程发送到信道然后退出。 协程不知道 (或不在乎) 是否接收到该值。 这意味着如果 ch 接收发生在超时之前, 协程不会永远徘徊。 timeout 信道最终由垃圾回收器回收并释放。

(在此示例中, 我们使用 time.Sleep 来演示协程和信道的机制。 在实际程序你应该使用 [time.After](https://golang.org/pkg/time/#After), 该函数会返回一个信道并在指定的持续时间后在该信道上发送。)

让我们看看这种模式另一种变化。 在下面的示例中, 我们有一个程序可以同时从多个拷贝的数据库中读取数据。 该程序只需要一个答案, 它应该接受最先到达的答案。

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
}

在此示例中, 闭包执行非阻塞发送, 这是通过在 default 情况下使用 select 语句中的发送逻辑来实现的。 如果发送无法立即进行, 那么将选择默认情况。 使发送变成非阻塞状态可以确保循环中启动的协程不会挂起。 但是, 如果结果在主函数执行完成之前到达, 那么发送可能会失败, 因为没有人准备好。

这个就是所谓的 竞态条件 问题的经典示例, 但解决办法非常简单。 我们只需要确保信道带缓冲 ch (通过设置 make 函数的的第二个参数即可设置缓冲长度), 确保信道中有首先发送的值的位置即可。 这样可以确保发送始终成功, 并且无论执行顺序如何, 都将检索第一个到达的值。

这两个示例演示了用 Go 协程可以非常简单的实现复杂的交互。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/go-blog/go-conc...

译文地址:https://learnku.com/docs/go-blog/go-conc...

上一篇 下一篇
Summer
贡献者:3
讨论数量: 0
发起讨论 只看当前版本


暂无话题~