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
通道缓冲了 1 个值的空间,允许超时 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
}
在这个例子中,闭包执行非阻塞发送,它通过在 select
语句中使用发送操作以及 default
案例来实现。如果发送不能立即执行,则将选择默认情况。使发送非阻塞保证循环中启动的任何 goroutine 不会挂起。但是,如果结果在主函数到达接收之前到达,则发送可能会失败,因为没有人准备好接收。
这个问题是所谓的 竞争条件 的一个典型例子,但解决方法很简单。我们只需确保缓冲通道 ch
(通过将缓冲区长度作为第二个参数添加到 make),从而保证第一个发送有一个地方放置值。这确保发送将始终成功,并且无论执行顺序如何,都会检索第一个到达的值。
这两个例子展示了 Go 可以多么简单地表达 goroutine 之间复杂的交互。
下一篇文章:真实的 Go 项目:SmartTwitter 和 web.go
上一篇文章:Go Playground 简介
博客索引