Go 博客
Go 并发模式:超时,继续
并发编程有其自身的惯用法。一个很好的例子就是超时。虽然 Go 的通道不直接支持超时,但它们很容易实现。假设我们想从通道 ch
接收,但最多想等待一秒钟才能收到值。我们会先创建一个信号通道,然后启动一个 goroutine,该 goroutine 会休眠一秒后再发送到通道。
timeout := make(chan bool, 1)
go func() {
time.Sleep(1 * time.Second)
timeout <- true
}()
然后,我们可以使用 select
语句从 ch
或 timeout
接收。如果在等待一秒钟后 ch
上没有收到任何数据,则会选择超时情况,读取 ch
的尝试将被放弃。
select {
case <-ch:
// a read from ch has occurred
case <-timeout:
// the read from ch has timed out
}
timeout
通道是带有一个值为缓冲的,允许超时 goroutine 发送到通道然后退出。该 goroutine 不知道(也不关心)值是否已被接收。这意味着,如果 ch
的接收发生在超时到达之前,该 goroutine 将不会永远挂起。timeout
通道最终将被垃圾收集器回收。
(在此示例中,我们使用了 time.Sleep
来演示 goroutine 和通道的机制。在实际程序中,您应该使用 [time.After](/pkg/time/#After),一个返回通道并在指定持续时间后在该通道上发送的函数。)
让我们看这个模式的另一个变体。在这个例子中,我们有一个程序,它同时从多个复制的数据库读取。程序只需要其中一个答案,并且它应该接受最先到达的答案。
函数 Query
接受一个数据库连接切片和一个 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
语句中的发送操作来实现。如果发送无法立即进行,将选择默认情况。使发送非阻塞可确保在循环中启动的任何 goroutine 都不会挂起。但是,如果结果在主函数到达接收之前到达,则发送可能会失败,因为没有人准备好接收。
此问题是众所周知的 竞争条件 的典型例子,但修复非常简单。我们只需确保缓冲通道 ch
(通过将缓冲区长度作为第二个参数添加到 make),从而保证第一次发送都有一个地方可以存放值。这确保了发送将始终成功,并且无论执行顺序如何,第一个到达的值都将被检索。
这两个示例演示了 Go 在表达 goroutine 之间复杂交互时的简单性。
下一篇文章:真实 Go 项目:SmartTwitter 和 web.go
上一篇文章:Go Playground 简介
博客索引