超时处理
超时对于一个连接外部资源,或者其它一些需要花费执行时间的操作的程序而言是很重要的。得益于通道和 select,在 Go 中实现超时操作是简洁而优雅的。
package main
import "time"
import "fmt"
func main() {
// 在我们的例子中,假如我们执行一个外部调用,并在 2 秒后
// 通过通道 `c1` 返回它的执行结果。
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
// 这里是使用 `select` 实现一个超时操作。
// `res := <- c1` 等待结果,`<-Time.After` 等待超时
// 时间 1 秒后发送的值。由于 `select` 默认处理第一个
// 已准备好的接收操作,如果这个操作超过了允许的 1 秒
// 的话,将会执行超时 case。
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
// 如果我允许一个长一点的超时时间 3 秒,将会成功的从 `c2`
// 接收到值,并且打印出结果。
c2 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c2 <- "result 2"
}()
select {
case res := <-c2:
fmt.Println(res)
case <-time.After(time.Second * 3):
fmt.Println("timeout 2")
}
}
使用这个 select
超时方式,需要使用通道传递结果。这对于一般情况是个好的方式,因为其他重要的 Go 特性是基于通道和select 的。接下来我们就要看到两个例子:timer 和 ticker。
非阻塞通道操作
常规的通过通道发送和接收数据是阻塞的。然而,我们可以使用带一个 default
子句的 select
来实现非阻塞的发送、接收,甚至是非阻塞的多路 select
。
package main
import "fmt"
func main() {
messages := make(chan string)
signals := make(chan bool)
// 这里是一个非阻塞接收的例子。如果在 `messages` 中
// 存在,然后 `select` 将这个值带入 `<-messages` `case`
// 中。如果不是,就直接到 `default` 分支中。
select {
case msg := <-messages:
fmt.Println("received message", msg)
default:
fmt.Println("no message received")
}
// 一个非阻塞发送的实现方法和上面一样。
msg := "hi"
select {
case messages <- msg:
fmt.Println("sent message", msg)
default:
fmt.Println("no message sent")
}
// 我们可以在 `default` 前使用多个 `case` 子句来实现
// 一个多路的非阻塞的选择器。这里我们视图在 `messages`
// 和 `signals` 上同时使用非阻塞的接受操作。
select {
case msg := <-messages:
fmt.Println("received message", msg)
case sig := <-signals:
fmt.Println("received signal", sig)
default:
fmt.Println("no activity")
}
}
通道的关闭
关闭一个通道意味着不能再向这个通道发送值了。这个特性可以用来给这个通道的接收方传达工作已将完成的信息。
package main
import "fmt"
// 在这个例子中,我们将使用一个 `jobs` 通道来传递 `main()` 中 Go
// 协程任务执行的结束信息到一个工作 Go 协程中。当我们没有多余的
// 任务给这个工作 Go 协程时,我们将 `close` 这个 `jobs` 通道。
func main() {
jobs := make(chan int, 5)
done := make(chan bool)
// 这是工作 Go 协程。使用 `j, more := <- jobs` 循环的从
// `jobs` 接收数据。在接收的这个特殊的二值形式的值中,
// 如果 `jobs` 已经关闭了,并且通道中所有的值都已经接收
// 完毕,那么 `more` 的值将是 `false`。当我们完成所有
// 的任务时,将使用这个特性通过 `done` 通道去进行通知。
go func() {
for {
j, more := <-jobs
if more {
fmt.Println("received job", j)
} else {
fmt.Println("received all jobs")
done <- true
return
}
}
}()
// 这里使用 `jobs` 发送 3 个任务到工作函数中,然后
// 关闭 `jobs`。
for j := 1; j <= 3; j++ {
jobs <- j
fmt.Println("sent job", j)
}
close(jobs)
fmt.Println("sent all jobs")
// 我们使用前面学到的通道同步方法等待任务结束。
<-done
}
通道遍历
在前面的例子中,我们讲过 for
和 range
为基本的数据结构提供了迭代的功能。我们也可以使用这个语法来遍历从通道中取得的值。
package main
import "fmt"
func main() {
// 我们将遍历在 `queue` 通道中的两个值。
queue := make(chan string, 2)
queue <- "one"
queue <- "two"
close(queue)
// 这个 `range` 迭代从 `queue` 中得到的每个值。因为我们
// 在前面 `close` 了这个通道,这个迭代会在接收完 2 个值
// 之后结束。如果我们没有 `close` 它,我们将在这个循环中
// 继续阻塞执行,等待接收第三个值
for elem := range queue {
fmt.Println(elem)
}
}
这个例子也让我们看到,一个非空的通道也是可以关闭的,但是通道中剩下的值仍然可以被接收到。