mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 03:34:15 +08:00
Merge pull request #204 from glight2000/master
14.5 14.6,终于见到 Able to merge了。。。
This commit is contained in:
@@ -174,4 +174,4 @@ func backent() {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[通道的同步:关闭通道-测试阻塞的通道](14.3.md)
|
||||
- 下一节:[通道,超时和计时器](14.5.md)
|
||||
- 下一节:[通道,超时和计时器(Ticker)](14.5.md)
|
||||
|
122
eBook/14.5.md
122
eBook/14.5.md
@@ -1,4 +1,4 @@
|
||||
# 14.5 通道,超时和计时器
|
||||
# 14.5 通道,超时和计时器(Ticker)
|
||||
|
||||
`time`包中有一些有趣的功能可以和通道组合使用。
|
||||
|
||||
@@ -39,13 +39,127 @@ rate_per_sec := 10
|
||||
var dur Duration = 1e9 / rate_per_sec
|
||||
chRate := time.Tick(dur) // a tick every 1/10th of a second
|
||||
for req := range requests {
|
||||
<- chRate // rate limit our Service.Method RPC calls
|
||||
go client.Call("Service.Method", req, ...)
|
||||
<- chRate // rate limit our Service.Method RPC calls
|
||||
go client.Call("Service.Method", req, ...)
|
||||
}
|
||||
```
|
||||
这样只会按照指定频率处理请求:`chRate`阻塞了更高的频率。每秒处理的频率可以根据机器负载(和/或)资源的情况而增加或减少。
|
||||
|
||||
问题14.1:扩展上边的代码,思考如何承载周期请求数的暴增(提示:使用带缓冲通道和计时器对象)。
|
||||
|
||||
定时器(TImer)结构体看上去和计时器(Ticker)结构体的确很像(构造为`NewTimer(d Duration)`)),但是它只发送一次时间,在`Dration d`之后。
|
||||
|
||||
还有`time.After(d)`函数,声明如下:
|
||||
```go
|
||||
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):
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tick := time.Tick(1e8)
|
||||
boom := time.After(5e8)
|
||||
for {
|
||||
select {
|
||||
case <-tick:
|
||||
fmt.Println("tick.")
|
||||
case <-boom:
|
||||
fmt.Println("BOOM!")
|
||||
return
|
||||
default:
|
||||
fmt.Println(" .")
|
||||
time.Sleep(5e7)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
输出:
|
||||
```
|
||||
.
|
||||
.
|
||||
tick.
|
||||
.
|
||||
.
|
||||
tick.
|
||||
.
|
||||
.
|
||||
tick.
|
||||
.
|
||||
.
|
||||
tick.
|
||||
.
|
||||
.
|
||||
tick.
|
||||
BOOM!
|
||||
```
|
||||
习惯用法:简单超时模式
|
||||
|
||||
要从通道`ch`中接收数据,但是最多等待1秒。先创建一个信号通道,然后启动一个`lambda`协程,协程在给通道发送数据之前是休眠的:
|
||||
```go
|
||||
timeout := make(chan bool, 1)
|
||||
go func() {
|
||||
time.Sleep(1e9) // one second
|
||||
timeout <- true
|
||||
}()
|
||||
```
|
||||
然后使用`select`语句接收`ch`或者`timeout`的数据:如果`ch`在1秒内没有收到数据,就选择到了`time`分支并放弃了`ch`的读取。
|
||||
```go
|
||||
select {
|
||||
case <-ch:
|
||||
// a read from ch has occured
|
||||
case <-timeout:
|
||||
// the read from ch has timed out
|
||||
break
|
||||
}
|
||||
```
|
||||
第二种形式:取消耗时很长的同步调用:
|
||||
|
||||
也可以使用`time.After()`函数替换`timeout-channel`。可以在`select`中使用以发送信号超时或停止协程的执行。以下代码,在`timeoutNs`纳秒后执行`select`的`timeout`分支时,`client.Call`不会给通道`ch`返回值:
|
||||
```go
|
||||
ch := make(chan error, 1)
|
||||
go func() { ch <- client.Call("Service.Method", args, &reply) } ()
|
||||
select {
|
||||
case resp := <-ch
|
||||
// use resp and reply
|
||||
case <-time.After(timeoutNs):
|
||||
// call timed out
|
||||
break
|
||||
}
|
||||
```
|
||||
注意缓冲大小设置为1是必要的,可以避免协程死锁以及确保超时的通道可以被垃圾回收。
|
||||
|
||||
第三种形式:假设程序从多个复制的数据库同时读取。只需要一个答案,需要接收首先到达的答案,`Query`函数获取数据库的连接切片并请求。并行请求每一个数据库并返回收到的第一个响应:
|
||||
```go
|
||||
func Query(conns []conn, query string) Result {
|
||||
ch := make(chan Result, 1)
|
||||
for _, conn := range conns {
|
||||
go func(c Conn) {
|
||||
select {
|
||||
case ch <- c.DoQuery(query):
|
||||
default:
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
return <- ch
|
||||
}
|
||||
```
|
||||
再次声明,结果通道`ch`必须是带缓冲的:以保证第一个发送进来的数据有地方可以存放,确保放入的首个数据总会成功,所以第一个到达的值会被获取而与执行的顺序无关。正在执行的协程可以总是可以使用`runtime.Goexit()`来停止。
|
||||
|
||||
|
||||
在应用中缓存数据:
|
||||
|
||||
应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:[http://www.tideland.biz/CachingValues](http://www.tideland.biz/CachingValues)(译者注:这个网页已经失效了)讨论了一种方式,使用协程和计时器对象来实现。
|
||||
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[使用select切换协程](14.4.md)
|
||||
- 下一节:[对协程使用recover](14.6.md)
|
||||
- 下一节:[协程和恢复(recover)](14.6.md)
|
||||
|
29
eBook/14.6.md
Normal file
29
eBook/14.6.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# 14.6 协程和恢复(recover)
|
||||
|
||||
一个用到`recover`的程序(参见章节13.3)停掉了服务器内部一个失败的协程而不影响其他协程的工作。
|
||||
```go
|
||||
func server(workChan <-chan *Work) {
|
||||
for work := range workChan {
|
||||
go safelyDo(work) // start the goroutine for that work
|
||||
}
|
||||
}
|
||||
|
||||
func safelyDo(work *Work) {
|
||||
defer func {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("Work failed with %s in %v", err, 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)找到。
|
||||
|
||||
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[通道,超时和计时器](14.5.md)
|
||||
- 下一节:[对比新旧模型:任务和工作](14.7.md)
|
@@ -140,6 +140,8 @@
|
||||
- 14.2 [使用通道进行协程间通信](14.2.md)
|
||||
- 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md)
|
||||
- 14.4 [使用select切换协程](14.4.md)
|
||||
- 14.5 [通道,超时和计时器(Ticker)](14.5.md)
|
||||
- 14.6 [协程和恢复(recover)](14.6.md)
|
||||
- 第15章:[网络、模版与网页应用](15.0.md)
|
||||
- 15.1 [tcp服务器](15.1.md)
|
||||
- 15.2 [一个简单的web服务器](15.2.md)
|
||||
|
Reference in New Issue
Block a user