14.5-14.6

This commit is contained in:
Unknwon
2016-01-06 05:32:15 +08:00
parent 7ccbf3915f
commit 5942213569
4 changed files with 47 additions and 26 deletions

View File

@@ -9,7 +9,7 @@
## 翻译进度 ## 翻译进度
14.4 [使用 select 切换协程](eBook/14.4.md) 14.6 [协程和恢复recover](eBook/14.6.md)
## 支持本书 ## 支持本书

View File

@@ -31,4 +31,4 @@ Golang 编程245386165
|更新日期 |更新内容 |更新日期 |更新内容
|----------|------------------ |----------|------------------
|2015-1-5|14.4 使用 select 切换协程 |2015-1-6|14.6 协程和恢复recover

View File

@@ -1,8 +1,9 @@
# 14.5 通道超时和计时器Ticker # 14.5 通道超时和计时器Ticker
`time`包中有一些有趣的功能可以和通道组合使用。 `time` 包中有一些有趣的功能可以和通道组合使用。
其中就包含了 `time.Ticker` 结构体,这个对象以指定的时间间隔重复的向通道 C 发送时间值:
其中就包含了`time.Ticker`结构体这个对象以指定的时间间隔重复的向通道C发送时间值
```go ```go
type Ticker struct { type Ticker struct {
C <-chan Time // the channel on which the ticks are delivered. C <-chan Time // the channel on which the ticks are delivered.
@@ -10,11 +11,13 @@ type Ticker struct {
... ...
} }
``` ```
时间间隔的单位是ns纳秒int64在工厂函数`time.NewTicker`中以`Duration`类型的参数传入:`func Newticker(dur) *Ticker`
时间间隔的单位是 ns纳秒int64在工厂函数 `time.NewTicker` 中以 `Duration` 类型的参数传入:`func Newticker(dur) *Ticker`
在协程周期性的执行一些事情(打印状态日志,输出,计算等等)的时候非常有用。 在协程周期性的执行一些事情(打印状态日志,输出,计算等等)的时候非常有用。
调用`Stop()`使计时器停止,在`defer`语句中使用。这些都很好的适应`select`语句: 调用 `Stop()` 使计时器停止,在 `defer` 语句中使用。这些都很好的适应 `select` 语句:
```go ```go
ticker := time.NewTicker(updateInterval) ticker := time.NewTicker(updateInterval)
defer ticker.Stop() defer ticker.Stop()
@@ -30,7 +33,8 @@ default: // no value ready to be received
... ...
} }
``` ```
`time.Tick()`函数声明为`Tick(d Duration) <-chan Time`当你想返回一个通道而不必关闭它的时候这个函数非常有用它以d为周期给返回的通道发送时间d是纳秒数。如果需要像下边的代码一样限制处理频率函数`client.Call()`是一个RPC调用这里暂不赘述参见章节[15.9](15.9.md)
`time.Tick()` 函数声明为 `Tick(d Duration) <-chan Time`,当你想返回一个通道而不必关闭它的时候这个函数非常有用:它以 d 为周期给返回的通道发送时间d是纳秒数。如果需要像下边的代码一样限制处理频率函数 `client.Call()` 是一个 RPC 调用,这里暂不赘述(参见第 [15.9](15.9.md) 节):
```go ```go
import "time" import "time"
@@ -43,19 +47,23 @@ for req := range requests {
go client.Call("Service.Method", req, ...) go client.Call("Service.Method", req, ...)
} }
``` ```
这样只会按照指定频率处理请求:`chRate`阻塞了更高的频率。每秒处理的频率可以根据机器负载(和/或)资源的情况而增加或减少。
问题14.1:扩展上边的代码,思考如何承载周期请求数的暴增(提示:使用带缓冲通道和计时器对象) 这样只会按照指定频率处理请求:`chRate` 阻塞了更高的频率。每秒处理的频率可以根据机器负载(和/或)资源的情况而增加或减少
定时器TImer结构体看上去和计时器Ticker结构体的确很像构造为`NewTimer(d Duration)`)),但是它只发送一次时间,在`Dration d`之后 问题 14.1:扩展上边的代码,思考如何承载周期请求数的暴增(提示:使用带缓冲通道和计时器对象)
定时器Timer结构体看上去和计时器Ticker结构体的确很像构造为 `NewTimer(d Duration)`),但是它只发送一次时间,在 `Dration d` 之后。
还有 `time.After(d)` 函数,声明如下:
还有`time.After(d)`函数,声明如下:
```go ```go
func After(d Duration) <-chan Time func After(d Duration) <-chan Time
``` ```
`Duration d`之后,当前时间被发到返回的通道;所以它和`NewTimer(d).C`是等价的;它类似`Tick()`,但是`After()`只发送一次时间。下边有个很具体的示例,很好的阐明了`select``default`的作用:
示例14.11[timer_goroutine.go](examples/chapter_14/timer_goroutine.go) `Duration d` 之后,当前时间被发到返回的通道;所以它和 `NewTimer(d).C` 是等价的;它类似 `Tick()`,但是 `After()` 只发送一次时间。下边有个很具体的示例,很好的阐明了 `select``default` 的作用
示例 14.11[timer_goroutine.go](examples/chapter_14/timer_goroutine.go)
```go ```go
package main package main
@@ -81,7 +89,9 @@ func main() {
} }
} }
``` ```
输出: 输出:
``` ```
. .
. .
@@ -100,9 +110,11 @@ tick.
tick. tick.
BOOM! BOOM!
``` ```
习惯用法:简单超时模式 习惯用法:简单超时模式
要从通道`ch`中接收数据但是最多等待1秒。先创建一个信号通道然后启动一个`lambda`协程,协程在给通道发送数据之前是休眠的: 要从通道 `ch` 中接收数据但是最多等待1秒。先创建一个信号通道然后启动一个 `lambda` 协程,协程在给通道发送数据之前是休眠的:
```go ```go
timeout := make(chan bool, 1) timeout := make(chan bool, 1)
go func() { go func() {
@@ -110,7 +122,9 @@ go func() {
timeout <- true timeout <- true
}() }()
``` ```
然后使用`select`语句接收`ch`或者`timeout`的数据:如果`ch`在1秒内没有收到数据就选择到了`time`分支并放弃了`ch`的读取。
然后使用 `select` 语句接收 `ch` 或者 `timeout` 的数据:如果 `ch` 在 1 秒内没有收到数据,就选择到了 `time` 分支并放弃了 `ch` 的读取。
```go ```go
select { select {
case <-ch: case <-ch:
@@ -120,9 +134,11 @@ select {
break break
} }
``` ```
第二种形式:取消耗时很长的同步调用:
也可以使用`time.After()`函数替换`timeout-channel`。可以在`select`中使用以发送信号超时或停止协程的执行。以下代码,在`timeoutNs`纳秒后执行`select``timeout`分支时,`client.Call`不会给通道`ch`返回值: 第二种形式:取消耗时很长的同步调用
也可以使用 `time.After()` 函数替换 `timeout-channel`。可以在 `select` 中使用以发送信号超时或停止协程的执行。以下代码,在 `timeoutNs` 纳秒后执行 `select``timeout` 分支时,`client.Call` 不会给通道 `ch` 返回值:
```go ```go
ch := make(chan error, 1) ch := make(chan error, 1)
go func() { ch <- client.Call("Service.Method", args, &reply) } () go func() { ch <- client.Call("Service.Method", args, &reply) } ()
@@ -134,9 +150,11 @@ case <-time.After(timeoutNs):
break break
} }
``` ```
注意缓冲大小设置为1是必要的可以避免协程死锁以及确保超时的通道可以被垃圾回收。
第三种形式:假设程序从多个复制的数据库同时读取。只需要一个答案,需要接收首先到达的答案,`Query`函数获取数据库的连接切片并请求。并行请求每一个数据库并返回收到的第一个响应: 注意缓冲大小设置为 1 是必要的,可以避免协程死锁以及确保超时的通道可以被垃圾回收。
第三种形式:假设程序从多个复制的数据库同时读取。只需要一个答案,需要接收首先到达的答案,`Query` 函数获取数据库的连接切片并请求。并行请求每一个数据库并返回收到的第一个响应:
```go ```go
func Query(conns []conn, query string) Result { func Query(conns []conn, query string) Result {
ch := make(chan Result, 1) ch := make(chan Result, 1)
@@ -151,12 +169,13 @@ func Query(conns []conn, query string) Result {
return <- ch return <- ch
} }
``` ```
再次声明,结果通道`ch`必须是带缓冲的:以保证第一个发送进来的数据有地方可以存放,确保放入的首个数据总会成功,所以第一个到达的值会被获取而与执行的顺序无关。正在执行的协程可以总是可以使用`runtime.Goexit()`来停止。
再次声明,结果通道 `ch` 必须是带缓冲的:以保证第一个发送进来的数据有地方可以存放,确保放入的首个数据总会成功,所以第一个到达的值会被获取而与执行的顺序无关。正在执行的协程可以总是可以使用 `runtime.Goexit()` 来停止。
在应用中缓存数据: 在应用中缓存数据:
应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:[http://www.tideland.biz/CachingValues](http://www.tideland.biz/CachingValues)(译者注:这个网页已经失效了)讨论了一种方式,使用协程和计时器对象来实现。 应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。
## 链接 ## 链接

View File

@@ -1,6 +1,7 @@
# 14.6 协程和恢复recover # 14.6 协程和恢复recover
一个用到`recover`的程序(参见章节13.3)停掉了服务器内部一个失败的协程而不影响其他协程的工作。 一个用到 `recover` 的程序(参见13.3)停掉了服务器内部一个失败的协程而不影响其他协程的工作。
```go ```go
func server(workChan <-chan *Work) { func server(workChan <-chan *Work) {
for work := range workChan { for work := range workChan {
@@ -17,9 +18,10 @@ func safelyDo(work *Work) {
do(work) do(work)
} }
``` ```
上边的代码,如果`do(work)`发生panic错误会被记录且协程会退出并释放而其他协程不受影响。
因为`recover`总是返回`nil`,除非直接在`defer`修饰的函数中调用,`defer`修饰的代码可以调用那些自身可以使用`panic`和`recover`避免失败的库例程(库函数)。举例,`safelyDo()`中`deffer`修饰的函数可能在调用`recover`之前就调用了一个`logging`函数,`panicking`状态不会影响`logging`代码的运行。因为加入了恢复模式,函数`do`(以及它调用的任何东西)可以通过调用`panic`来摆脱不好的情况。但是恢复是在`panicking`的协程内部的:不能被另外一个协程恢复。更多深入的细节处理可以在[http://www.tideland.biz/SupervisingGoroutines](http://www.tideland.biz/SupervisingGoroutines)ref.43)找到。 上边的代码,如果 `do(work)` 发生 panic错误会被记录且协程会退出并释放而其他协程不受影响
因为 `recover` 总是返回 `nil`,除非直接在 `defer` 修饰的函数中调用,`defer` 修饰的代码可以调用那些自身可以使用 `panic``recover` 避免失败的库例程(库函数)。举例,`safelyDo()``deffer` 修饰的函数可能在调用 `recover` 之前就调用了一个 `logging` 函数,`panicking` 状态不会影响 `logging` 代码的运行。因为加入了恢复模式,函数 `do`(以及它调用的任何东西)可以通过调用 `panic` 来摆脱不好的情况。但是恢复是在 `panicking` 的协程内部的:不能被另外一个协程恢复。
## 链接 ## 链接