From 1412b66ec4dcdf67b22676ea9882c6ce5c88dd97 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:18:15 +0800 Subject: [PATCH 01/31] Update 14.5.md --- eBook/14.5.md | 122 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 4 deletions(-) diff --git a/eBook/14.5.md b/eBook/14.5.md index b01c8a3..998cc66 100644 --- a/eBook/14.5.md +++ b/eBook/14.5.md @@ -1,4 +1,4 @@ -# 14.5 通道,超时和计时器 +# 14.5 通道,超时和断续器(Ticker) `time`包中有一些有趣的功能可以和通道组合使用。 @@ -14,7 +14,7 @@ type Ticker struct { 在协程周期性的执行一些事情(打印状态日志,输出,计算等等)的时候非常有用。 -调用`Stop()`使计时器停止,在`defer`语句中使用。这些都很好的适应`select`语句: +调用`Stop()`使断续器停止,在`defer`语句中使用。这些都很好的适应`select`语句: ```go ticker := time.NewTicker(updateInterval) defer ticker.Stop() @@ -39,10 +39,124 @@ 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`讨论了一种方式,使用协程和断续器对象来实现。 ## 链接 From c4299f26aa003a1cc687eb51652093361f71403a Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:19:09 +0800 Subject: [PATCH 02/31] Update directory.md --- eBook/directory.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eBook/directory.md b/eBook/directory.md index 70eccbf..8a0284d 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -140,6 +140,7 @@ - 14.2 [使用通道进行协程间通信](14.2.md) - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) - 14.4 [使用select切换协程](14.4.md) + - 14.5 [通道,超时和断续器(Ticker)](14.5.md) - 第15章:[网络、模版与网页应用](15.0.md) - 15.1 [tcp服务器](15.1.md) - 15.2 [一个简单的web服务器](15.2.md) From 98e44e95323c21a9ab733b29fe296b71c7862f7d Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:19:45 +0800 Subject: [PATCH 03/31] Update 14.4.md --- eBook/14.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.4.md b/eBook/14.4.md index 4b641a7..4f34b70 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -174,4 +174,4 @@ func backent() { - [目录](directory.md) - 上一节:[通道的同步:关闭通道-测试阻塞的通道](14.3.md) -- 下一节:[通道,超时和计时器](14.5.md) +- 下一节:[通道,超时和断续器(Ticker)](14.5.md) From 6963005cca216807779c0e42460adea4cc9a9b45 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:29:40 +0800 Subject: [PATCH 04/31] Update 14.5.md --- eBook/14.5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eBook/14.5.md b/eBook/14.5.md index 998cc66..96aa8c9 100644 --- a/eBook/14.5.md +++ b/eBook/14.5.md @@ -156,10 +156,10 @@ func Query(conns []conn, query string) Result { 在应用中缓存数据: -应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:`http://www.tideland.biz/CachingValues`讨论了一种方式,使用协程和断续器对象来实现。 +应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:[http://www.tideland.biz/CachingValues](http://www.tideland.biz/CachingValues)(译者注:这个网页已经失效了)讨论了一种方式,使用协程和断续器对象来实现。 ## 链接 - [目录](directory.md) - 上一节:[使用select切换协程](14.4.md) -- 下一节:[对协程使用recover](14.6.md) +- 下一节:[协程和recover](14.6.md) From 6e2db36afe91ba202011e4445f650464374e8c98 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:37:13 +0800 Subject: [PATCH 05/31] Create 14.6.md --- eBook/14.6.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 eBook/14.6.md diff --git a/eBook/14.6.md b/eBook/14.6.md new file mode 100644 index 0000000..ef80b43 --- /dev/null +++ b/eBook/14.6.md @@ -0,0 +1,7 @@ +# 14.6 协程和recover + +## 链接 + +- [目录](directory.md) +- 上一节:[通道,超时和计时器](14.5.md) +- 下一节:[对比新旧模型:任务和工作](14.7.md) From bee0ec78c31c2fe59403301adac241a1bc401d90 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:38:29 +0800 Subject: [PATCH 06/31] Update directory.md --- eBook/directory.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eBook/directory.md b/eBook/directory.md index 8a0284d..010a0a7 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -141,6 +141,7 @@ - 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) From ff9e6f00815e950f54cea5c6a6a1f042d5165829 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:44:39 +0800 Subject: [PATCH 07/31] Update 14.6.md --- eBook/14.6.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/eBook/14.6.md b/eBook/14.6.md index ef80b43..7618cb9 100644 --- a/eBook/14.6.md +++ b/eBook/14.6.md @@ -1,5 +1,23 @@ # 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) +} +``` + ## 链接 - [目录](directory.md) From 6fb834a45dcdb8583a9f0bb43d404025194f7e66 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:18:33 +0800 Subject: [PATCH 08/31] =?UTF-8?q?=E5=AE=8C=E6=88=9014.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eBook/14.6.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eBook/14.6.md b/eBook/14.6.md index 7618cb9..7db7f39 100644 --- a/eBook/14.6.md +++ b/eBook/14.6.md @@ -17,6 +17,10 @@ func safelyDo(work *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)找到。 + ## 链接 From 643e91e33dc1364076cdb5cdc012ff5df4a0f5bc Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:19:22 +0800 Subject: [PATCH 09/31] 14.5 14.6 --- README_gc.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README_gc.md b/README_gc.md index 9626c6f..7107e50 100644 --- a/README_gc.md +++ b/README_gc.md @@ -38,3 +38,4 @@ Golang 编程:245386165 |2015-12-25|14.1 |2015-12-28|14.2 |2015-12-30|14.3 14.4 +|2015-12-31|14.5 14.6 From b5f7a99c871ea6f187d8f2ed03b633de74139ab1 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:21:13 +0800 Subject: [PATCH 10/31] Update 14.5.md --- eBook/14.5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.5.md b/eBook/14.5.md index 96aa8c9..16879e7 100644 --- a/eBook/14.5.md +++ b/eBook/14.5.md @@ -162,4 +162,4 @@ func Query(conns []conn, query string) Result { - [目录](directory.md) - 上一节:[使用select切换协程](14.4.md) -- 下一节:[协程和recover](14.6.md) +- 下一节:[协程和恢复(recover)](14.6.md) From 4b7cb223110e2bf372fccf4eea0b04d3a3e1650d Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:21:27 +0800 Subject: [PATCH 11/31] Update 14.6.md --- eBook/14.6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.6.md b/eBook/14.6.md index 7db7f39..0bd6ee1 100644 --- a/eBook/14.6.md +++ b/eBook/14.6.md @@ -1,4 +1,4 @@ -# 14.6 协程和recover +# 14.6 协程和恢复(recover) 一个用到`recover`的程序(参见章节13.3)停掉了服务器内部一个失败的协程而不影响其他协程的工作。 ```go From 2c6cc808a05356387e92c6425a541d2b2f5b17cd Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:21:57 +0800 Subject: [PATCH 12/31] Update directory.md --- eBook/directory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/directory.md b/eBook/directory.md index 010a0a7..029ffca 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -141,7 +141,7 @@ - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) - 14.4 [使用select切换协程](14.4.md) - 14.5 [通道,超时和断续器(Ticker)](14.5.md) - - 14.6 [协程和recover](14.6.md) + - 14.6 [协程和恢复(recover)](14.6.md) - 第15章:[网络、模版与网页应用](15.0.md) - 15.1 [tcp服务器](15.1.md) - 15.2 [一个简单的web服务器](15.2.md) From 13860c9ceaa1565834ec09af7afcebb357c6f624 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Sat, 2 Jan 2016 01:20:21 +0800 Subject: [PATCH 13/31] Update directory.md --- eBook/directory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/directory.md b/eBook/directory.md index 029ffca..6599fb7 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -140,7 +140,7 @@ - 14.2 [使用通道进行协程间通信](14.2.md) - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) - 14.4 [使用select切换协程](14.4.md) - - 14.5 [通道,超时和断续器(Ticker)](14.5.md) + - 14.5 [通道,超时和计时器(Ticker)](14.5.md) - 14.6 [协程和恢复(recover)](14.6.md) - 第15章:[网络、模版与网页应用](15.0.md) - 15.1 [tcp服务器](15.1.md) From 131cfea96e31fd5b02a6a24d22888800a409727d Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Sat, 2 Jan 2016 01:21:49 +0800 Subject: [PATCH 14/31] Update 14.5.md --- eBook/14.5.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eBook/14.5.md b/eBook/14.5.md index 16879e7..80b9ce8 100644 --- a/eBook/14.5.md +++ b/eBook/14.5.md @@ -1,4 +1,4 @@ -# 14.5 通道,超时和断续器(Ticker) +# 14.5 通道,超时和计时器(Ticker) `time`包中有一些有趣的功能可以和通道组合使用。 @@ -14,7 +14,7 @@ type Ticker struct { 在协程周期性的执行一些事情(打印状态日志,输出,计算等等)的时候非常有用。 -调用`Stop()`使断续器停止,在`defer`语句中使用。这些都很好的适应`select`语句: +调用`Stop()`使计时器停止,在`defer`语句中使用。这些都很好的适应`select`语句: ```go ticker := time.NewTicker(updateInterval) defer ticker.Stop() @@ -45,9 +45,9 @@ for req := range requests { ``` 这样只会按照指定频率处理请求:`chRate`阻塞了更高的频率。每秒处理的频率可以根据机器负载(和/或)资源的情况而增加或减少。 -问题14.1:扩展上边的代码,思考如何承载周期请求数的暴增(提示:使用带缓冲通道和断续器对象)。 +问题14.1:扩展上边的代码,思考如何承载周期请求数的暴增(提示:使用带缓冲通道和计时器对象)。 -计时器(TImer)结构体看上去和断续器(Ticker)结构体的确很像(构造为`NewTimer(d Duration)`)),但是它只发送一次时间,在`Dration d`之后。 +定时器(TImer)结构体看上去和计时器(Ticker)结构体的确很像(构造为`NewTimer(d Duration)`)),但是它只发送一次时间,在`Dration d`之后。 还有`time.After(d)`函数,声明如下: ```go @@ -156,7 +156,7 @@ func Query(conns []conn, query string) Result { 在应用中缓存数据: -应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:[http://www.tideland.biz/CachingValues](http://www.tideland.biz/CachingValues)(译者注:这个网页已经失效了)讨论了一种方式,使用协程和断续器对象来实现。 +应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:[http://www.tideland.biz/CachingValues](http://www.tideland.biz/CachingValues)(译者注:这个网页已经失效了)讨论了一种方式,使用协程和计时器对象来实现。 ## 链接 From e396ee49865da03d441effe759995ebde08cd039 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Sat, 2 Jan 2016 01:23:43 +0800 Subject: [PATCH 15/31] Update 14.4.md --- eBook/14.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.4.md b/eBook/14.4.md index 4f34b70..d241add 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -174,4 +174,4 @@ func backent() { - [目录](directory.md) - 上一节:[通道的同步:关闭通道-测试阻塞的通道](14.3.md) -- 下一节:[通道,超时和断续器(Ticker)](14.5.md) +- 下一节:[通道,超时和计时器(Ticker)](14.5.md) From 1f9cf7713b475e2251e2b0b9d836bd2a66ff9848 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:18:15 +0800 Subject: [PATCH 16/31] Update 14.5.md --- eBook/14.5.md | 122 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 4 deletions(-) diff --git a/eBook/14.5.md b/eBook/14.5.md index b01c8a3..998cc66 100644 --- a/eBook/14.5.md +++ b/eBook/14.5.md @@ -1,4 +1,4 @@ -# 14.5 通道,超时和计时器 +# 14.5 通道,超时和断续器(Ticker) `time`包中有一些有趣的功能可以和通道组合使用。 @@ -14,7 +14,7 @@ type Ticker struct { 在协程周期性的执行一些事情(打印状态日志,输出,计算等等)的时候非常有用。 -调用`Stop()`使计时器停止,在`defer`语句中使用。这些都很好的适应`select`语句: +调用`Stop()`使断续器停止,在`defer`语句中使用。这些都很好的适应`select`语句: ```go ticker := time.NewTicker(updateInterval) defer ticker.Stop() @@ -39,10 +39,124 @@ 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`讨论了一种方式,使用协程和断续器对象来实现。 ## 链接 From 1d197ce9b348456176c34693cdea5505d7b05285 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:19:09 +0800 Subject: [PATCH 17/31] Update directory.md --- eBook/directory.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eBook/directory.md b/eBook/directory.md index 041c98b..615aa43 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -140,6 +140,7 @@ - 14.2 [使用通道进行协程间通信](14.2.md) - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) - 14.4 [使用select切换协程](14.4.md) + - 14.5 [通道,超时和断续器(Ticker)](14.5.md) - 第15章:[网络、模版与网页应用](15.0.md) - 15.1 [tcp服务器](15.1.md) - 15.2 [一个简单的web服务器](15.2.md) From 295ab612746f1a4d1ddf71850cac468a4a48002e Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:19:45 +0800 Subject: [PATCH 18/31] Update 14.4.md --- eBook/14.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.4.md b/eBook/14.4.md index 4b641a7..4f34b70 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -174,4 +174,4 @@ func backent() { - [目录](directory.md) - 上一节:[通道的同步:关闭通道-测试阻塞的通道](14.3.md) -- 下一节:[通道,超时和计时器](14.5.md) +- 下一节:[通道,超时和断续器(Ticker)](14.5.md) From 898104651cb54be2698150cec97589eb4bfb4d0e Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:29:40 +0800 Subject: [PATCH 19/31] Update 14.5.md --- eBook/14.5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eBook/14.5.md b/eBook/14.5.md index 998cc66..96aa8c9 100644 --- a/eBook/14.5.md +++ b/eBook/14.5.md @@ -156,10 +156,10 @@ func Query(conns []conn, query string) Result { 在应用中缓存数据: -应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:`http://www.tideland.biz/CachingValues`讨论了一种方式,使用协程和断续器对象来实现。 +应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:[http://www.tideland.biz/CachingValues](http://www.tideland.biz/CachingValues)(译者注:这个网页已经失效了)讨论了一种方式,使用协程和断续器对象来实现。 ## 链接 - [目录](directory.md) - 上一节:[使用select切换协程](14.4.md) -- 下一节:[对协程使用recover](14.6.md) +- 下一节:[协程和recover](14.6.md) From 8d95ac44b20cf4fd9e112a98f00b447856b69ed2 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:37:13 +0800 Subject: [PATCH 20/31] Create 14.6.md --- eBook/14.6.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 eBook/14.6.md diff --git a/eBook/14.6.md b/eBook/14.6.md new file mode 100644 index 0000000..ef80b43 --- /dev/null +++ b/eBook/14.6.md @@ -0,0 +1,7 @@ +# 14.6 协程和recover + +## 链接 + +- [目录](directory.md) +- 上一节:[通道,超时和计时器](14.5.md) +- 下一节:[对比新旧模型:任务和工作](14.7.md) From e25f7b12ac6b47dd64e18a8f6e9593950769dc1d Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:38:29 +0800 Subject: [PATCH 21/31] Update directory.md --- eBook/directory.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eBook/directory.md b/eBook/directory.md index 615aa43..4e8e6d5 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -141,6 +141,7 @@ - 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) From 0351c39f67787123e19eb749dc34f90b5970a3f4 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 15:44:39 +0800 Subject: [PATCH 22/31] Update 14.6.md --- eBook/14.6.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/eBook/14.6.md b/eBook/14.6.md index ef80b43..7618cb9 100644 --- a/eBook/14.6.md +++ b/eBook/14.6.md @@ -1,5 +1,23 @@ # 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) +} +``` + ## 链接 - [目录](directory.md) From 008c6ced4d7c60114426d7c01e28e7cb8d955b0a Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:18:33 +0800 Subject: [PATCH 23/31] =?UTF-8?q?=E5=AE=8C=E6=88=9014.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eBook/14.6.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eBook/14.6.md b/eBook/14.6.md index 7618cb9..7db7f39 100644 --- a/eBook/14.6.md +++ b/eBook/14.6.md @@ -17,6 +17,10 @@ func safelyDo(work *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)找到。 + ## 链接 From f6ca63fe84f8f5807e565aa6daba52d9ff1c4fdb Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:19:22 +0800 Subject: [PATCH 24/31] 14.5 14.6 --- README_gc.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README_gc.md b/README_gc.md index 282c4b1..7107e50 100644 --- a/README_gc.md +++ b/README_gc.md @@ -31,4 +31,11 @@ Golang 编程:245386165 |更新日期 |更新内容 |----------|------------------ -|2015-12-31|14.1 并发、并行和协程 +|2015-11-25|13.10 性能调试:分析并优化 Go 程序 +|2015-12-10|15.0 15.1 15.2 +|2015-12-12|15.3 +|2015-12-22|15.4 +|2015-12-25|14.1 +|2015-12-28|14.2 +|2015-12-30|14.3 14.4 +|2015-12-31|14.5 14.6 From 3146530284ef8deb64e704f709f6d46b41ffae71 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:21:13 +0800 Subject: [PATCH 25/31] Update 14.5.md --- eBook/14.5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.5.md b/eBook/14.5.md index 96aa8c9..16879e7 100644 --- a/eBook/14.5.md +++ b/eBook/14.5.md @@ -162,4 +162,4 @@ func Query(conns []conn, query string) Result { - [目录](directory.md) - 上一节:[使用select切换协程](14.4.md) -- 下一节:[协程和recover](14.6.md) +- 下一节:[协程和恢复(recover)](14.6.md) From cafe1a16147b3c34160c90df25696c8e47a080b9 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:21:27 +0800 Subject: [PATCH 26/31] Update 14.6.md --- eBook/14.6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.6.md b/eBook/14.6.md index 7db7f39..0bd6ee1 100644 --- a/eBook/14.6.md +++ b/eBook/14.6.md @@ -1,4 +1,4 @@ -# 14.6 协程和recover +# 14.6 协程和恢复(recover) 一个用到`recover`的程序(参见章节13.3)停掉了服务器内部一个失败的协程而不影响其他协程的工作。 ```go From cb075948d5f08b1e6d81268a0878f2186c494ac6 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Thu, 31 Dec 2015 23:21:57 +0800 Subject: [PATCH 27/31] Update directory.md --- eBook/directory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/directory.md b/eBook/directory.md index 4e8e6d5..508182c 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -141,7 +141,7 @@ - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) - 14.4 [使用select切换协程](14.4.md) - 14.5 [通道,超时和断续器(Ticker)](14.5.md) - - 14.6 [协程和recover](14.6.md) + - 14.6 [协程和恢复(recover)](14.6.md) - 第15章:[网络、模版与网页应用](15.0.md) - 15.1 [tcp服务器](15.1.md) - 15.2 [一个简单的web服务器](15.2.md) From 1f0379a78ea24952ea935d052a40de5244edc831 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Sat, 2 Jan 2016 01:20:21 +0800 Subject: [PATCH 28/31] Update directory.md --- eBook/directory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/directory.md b/eBook/directory.md index 508182c..f382e9e 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -140,7 +140,7 @@ - 14.2 [使用通道进行协程间通信](14.2.md) - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) - 14.4 [使用select切换协程](14.4.md) - - 14.5 [通道,超时和断续器(Ticker)](14.5.md) + - 14.5 [通道,超时和计时器(Ticker)](14.5.md) - 14.6 [协程和恢复(recover)](14.6.md) - 第15章:[网络、模版与网页应用](15.0.md) - 15.1 [tcp服务器](15.1.md) From 5dac67664527fc126da00d63f577c3123204f8fb Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Sat, 2 Jan 2016 01:21:49 +0800 Subject: [PATCH 29/31] Update 14.5.md --- eBook/14.5.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eBook/14.5.md b/eBook/14.5.md index 16879e7..80b9ce8 100644 --- a/eBook/14.5.md +++ b/eBook/14.5.md @@ -1,4 +1,4 @@ -# 14.5 通道,超时和断续器(Ticker) +# 14.5 通道,超时和计时器(Ticker) `time`包中有一些有趣的功能可以和通道组合使用。 @@ -14,7 +14,7 @@ type Ticker struct { 在协程周期性的执行一些事情(打印状态日志,输出,计算等等)的时候非常有用。 -调用`Stop()`使断续器停止,在`defer`语句中使用。这些都很好的适应`select`语句: +调用`Stop()`使计时器停止,在`defer`语句中使用。这些都很好的适应`select`语句: ```go ticker := time.NewTicker(updateInterval) defer ticker.Stop() @@ -45,9 +45,9 @@ for req := range requests { ``` 这样只会按照指定频率处理请求:`chRate`阻塞了更高的频率。每秒处理的频率可以根据机器负载(和/或)资源的情况而增加或减少。 -问题14.1:扩展上边的代码,思考如何承载周期请求数的暴增(提示:使用带缓冲通道和断续器对象)。 +问题14.1:扩展上边的代码,思考如何承载周期请求数的暴增(提示:使用带缓冲通道和计时器对象)。 -计时器(TImer)结构体看上去和断续器(Ticker)结构体的确很像(构造为`NewTimer(d Duration)`)),但是它只发送一次时间,在`Dration d`之后。 +定时器(TImer)结构体看上去和计时器(Ticker)结构体的确很像(构造为`NewTimer(d Duration)`)),但是它只发送一次时间,在`Dration d`之后。 还有`time.After(d)`函数,声明如下: ```go @@ -156,7 +156,7 @@ func Query(conns []conn, query string) Result { 在应用中缓存数据: -应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:[http://www.tideland.biz/CachingValues](http://www.tideland.biz/CachingValues)(译者注:这个网页已经失效了)讨论了一种方式,使用协程和断续器对象来实现。 +应用程序中用到了来自数据库(或者常见的数据存储)的数据时,经常会把数据缓存到内存中,因为从数据库中获取数据的操作代价很高;如果数据库中的值不发生变化就没有问题。但是如果值有变化,我们需要一个机制来周期性的从数据库重新读取这些值:缓存的值就不可用(过期)了,而且我们也不希望用户看到陈旧的数据。这篇文章:[http://www.tideland.biz/CachingValues](http://www.tideland.biz/CachingValues)(译者注:这个网页已经失效了)讨论了一种方式,使用协程和计时器对象来实现。 ## 链接 From 229a2b00cef5f2020205eb15b0e997ca7ccce877 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Sat, 2 Jan 2016 01:23:43 +0800 Subject: [PATCH 30/31] Update 14.4.md --- eBook/14.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.4.md b/eBook/14.4.md index 4f34b70..d241add 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -174,4 +174,4 @@ func backent() { - [目录](directory.md) - 上一节:[通道的同步:关闭通道-测试阻塞的通道](14.3.md) -- 下一节:[通道,超时和断续器(Ticker)](14.5.md) +- 下一节:[通道,超时和计时器(Ticker)](14.5.md) From 4b5a95055e03554282e0a5f3a4c4e4fbd029c0b0 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Sun, 3 Jan 2016 11:02:59 +0800 Subject: [PATCH 31/31] Update README_gc.md --- README_gc.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README_gc.md b/README_gc.md index 7107e50..282c4b1 100644 --- a/README_gc.md +++ b/README_gc.md @@ -31,11 +31,4 @@ Golang 编程:245386165 |更新日期 |更新内容 |----------|------------------ -|2015-11-25|13.10 性能调试:分析并优化 Go 程序 -|2015-12-10|15.0 15.1 15.2 -|2015-12-12|15.3 -|2015-12-22|15.4 -|2015-12-25|14.1 -|2015-12-28|14.2 -|2015-12-30|14.3 14.4 -|2015-12-31|14.5 14.6 +|2015-12-31|14.1 并发、并行和协程