From e2e9c6bd30c8b566b5bf848a296f733001e4a279 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 10:52:04 +0800 Subject: [PATCH 01/21] Create 14.3.md --- eBook/14.3.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 eBook/14.3.md diff --git a/eBook/14.3.md b/eBook/14.3.md new file mode 100644 index 0000000..4020dbe --- /dev/null +++ b/eBook/14.3.md @@ -0,0 +1 @@ +# 协程的同步:关闭通道-测试阻塞的通道 From 1f352b20ea3eb05ea82a540f80710159ba7476fc Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 10:52:38 +0800 Subject: [PATCH 02/21] Update 14.2.md --- eBook/14.2.md | 1 - 1 file changed, 1 deletion(-) diff --git a/eBook/14.2.md b/eBook/14.2.md index 3db35a1..2d42fd4 100644 --- a/eBook/14.2.md +++ b/eBook/14.2.md @@ -626,7 +626,6 @@ func main() { } ``` - ## 链接 - [目录](directory.md) From dab7a34ab6865bacfe1401a05ab703d20712af07 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 11:24:27 +0800 Subject: [PATCH 03/21] Update 14.3.md --- eBook/14.3.md | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/eBook/14.3.md b/eBook/14.3.md index 4020dbe..b3d12fc 100644 --- a/eBook/14.3.md +++ b/eBook/14.3.md @@ -1 +1,110 @@ # 协程的同步:关闭通道-测试阻塞的通道 + +通道可以被显示的关闭;尽管它们和文件不同:不必每次都关闭。只有在当需要告诉接收者不会再提供新的值的时候,才需要关闭通道。只有发送者需要关闭通道,接收者永远不会需要。 + +继续看示例[goroutine2.go](examples/chapter_14/goroutine2.go)(示例14.2):我们如何在通道的`sendData()`完成的时候发送一个信号,`getData()`又如何检测到通道是否关闭或阻塞? + +第一个可以通过函数`close(ch)`来完成:这个将通道标记为无法通过发送操作<-接受更多的值;给已经关闭的通道发送或者再次关闭都会导致运行时的panic。在创建一个通道后使用defer语句是个不错的办法(类似这种情况): +```go +ch := make(chan float64) +defer close(ch) +``` +第二个问题可以使用逗号,ok操作符:用来检测通道是否被关闭。 + +如何来检测可以收到没有被阻塞(或者通道没有被关闭)? +```go +v, ok := <-ch // ok is true if v received value +``` +通常和if语句一起使用: +```go +if v, ok := <-ch; ok { + process(v) +} +``` +或者在for循环中接收的时候,当关闭或者阻塞的时候使用break: +```go +v, ok := <-ch +if !ok { + break +} +process(v) +``` +可以通过`_ = ch <- v`来实现非阻塞发送,因为空标识符获取到了发送给`ch`的任何东西。在示例程序14.2中使用这些可以改进为版本goroutine3.go,输出相同。 + +实现非阻塞通道的读取,需要使用select(参见章节[14.4](14.4.md)) + +示例 14.9-[goroutine3.go](examples/chapter_14/goroutine3.go) +```go +package main + +import "fmt" + +func main() { + ch := make(chan string) + go sendData(ch) + getData(ch) +} + +func sendData(ch chan string) { + ch <- "Washington" + ch <- "Tripoli" + ch <- "London" + ch <- "Beijing" + ch <- "Tokio" + close(ch) +} + +func getData(ch chan string) { + for { + input, open := <-ch + if !open { + break + } + fmt.Printf("%s ", input) + } +} +``` +改变了以下代码: +* 现在只有`sendData()`是协程,`getData()`和`main()`在同一个线程中: +```go +go sendData(ch) +getData(ch) +``` +* 在`sendData()`函数的最后,关闭了通道: +```go +func sendData(ch chan string) { + ch <- "Washington" + ch <- "Tripoli" + ch <- "London" + ch <- "Beijing" + ch <- "Tokio" + close(ch) +} +``` +* 在for循环的`getData()`中,在每次接收通道的数据之前都使用`if !open`来检测: +```go +for { + input, open := <-ch + if !open { + break + } + fmt.Printf("%s ", input) + } +``` +使用for-range语句来读取通道是更好的办法,因为这会自动检测通道是否关闭: +```go +for input := range ch { + process(input) +} +``` +阻塞和生产者-消费者模式: + + + + + +## 链接 + +- [目录](directory.md) +- 上一节:[协程间的信道](14.2.md) +- 下一节:[使用select切换协程](14.4.md) From ffc52b698fa6fb5710b5be31e984b9485e11ecf9 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 11:24:54 +0800 Subject: [PATCH 04/21] Update 14.3.md --- eBook/14.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.3.md b/eBook/14.3.md index b3d12fc..e2e2372 100644 --- a/eBook/14.3.md +++ b/eBook/14.3.md @@ -94,7 +94,7 @@ for { 使用for-range语句来读取通道是更好的办法,因为这会自动检测通道是否关闭: ```go for input := range ch { - process(input) + process(input) } ``` 阻塞和生产者-消费者模式: From 9c8a85c98b669a2d62201277441a81a415e121c6 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 14:23:01 +0800 Subject: [PATCH 05/21] =?UTF-8?q?=E5=AE=8C=E6=88=90=2014.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eBook/14.3.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eBook/14.3.md b/eBook/14.3.md index e2e2372..632f25d 100644 --- a/eBook/14.3.md +++ b/eBook/14.3.md @@ -1,4 +1,4 @@ -# 协程的同步:关闭通道-测试阻塞的通道 +# 14.3 协程的同步:关闭通道-测试阻塞的通道 通道可以被显示的关闭;尽管它们和文件不同:不必每次都关闭。只有在当需要告诉接收者不会再提供新的值的时候,才需要关闭通道。只有发送者需要关闭通道,接收者永远不会需要。 @@ -99,9 +99,9 @@ for input := range ch { ``` 阻塞和生产者-消费者模式: +在章节14.2.10的通道迭代器中,两个协程经常是一个阻塞另外一个。如果程序工作在多核心的机器上,大部分时间只用到了一个处理器。可以通过使用带缓冲(缓冲空间大于0)的通道来改善。比如,缓冲大小为100,迭代器在阻塞之前,至少可以从容器获得100个元素。如果消费者协程在独立的内核运行,就有可能让协程不会出现阻塞。 - - +由于容器中元素的数量通常是已知的,需要让通道有足够的容量放置所有的元素。这样,迭代器就不会阻塞(尽管消费者协程仍然可能阻塞)。然后,这样有效的加倍了迭代容器所需要的内存使用量,所以通道的容量需要限制一下最大值。记录运行时间和性能测试可以帮助你找到最小的缓存容量带来最好的性能。 ## 链接 From e30ff8107933ba5a3ba16112a38e786c31593900 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 14:34:53 +0800 Subject: [PATCH 06/21] Create 14.4 --- eBook/14.4 | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 eBook/14.4 diff --git a/eBook/14.4 b/eBook/14.4 new file mode 100644 index 0000000..b509708 --- /dev/null +++ b/eBook/14.4 @@ -0,0 +1,8 @@ +# 14.4 使用select切换协程 + + +## 链接 + +- [目录](directory.md) +- 上一节:[通道的同步:关闭通道-测试阻塞的通道](14.3.md) +- 下一节:[通道,超时和计时器](14.5.md) From ae5dbe4a0651a44833834060bf7f3ac71cb1f655 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 14:36:19 +0800 Subject: [PATCH 07/21] Rename 14.4 to 14.4.md --- eBook/{14.4 => 14.4.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename eBook/{14.4 => 14.4.md} (100%) diff --git a/eBook/14.4 b/eBook/14.4.md similarity index 100% rename from eBook/14.4 rename to eBook/14.4.md From e357fc09c2d164cfe815fd52e1a7a53df843fb3d Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 15:18:30 +0800 Subject: [PATCH 08/21] Update 14.4.md --- eBook/14.4.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/eBook/14.4.md b/eBook/14.4.md index b509708..516b43d 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -1,5 +1,89 @@ # 14.4 使用select切换协程 +从不不同的并发执行的协程中获取值可以通过关键字`select`来完成,它和`switch`控制语句非常相似(章节5.3)也被称作通信开关;它的行为像是“你准备好了吗”的轮询机制;`select`监听进入通道的数据,也可以是用通道发送值的时候。 +```go +select { +case u:= <- ch1: + ... +case v:= <- ch2: + ... + ... +default: // no value ready to be received + ... +} +``` +`default`语句是可选的;fallthrough行为,和普通的switch相似,是不允许的。在任何一个case中执行`break`或者`return`,select就结束了。 + +`select`做得就是:选择处理列出的多个通信情况中的一个。 +* 如果都阻塞了,会等待直到其中一个可以处理 +* 如果多个可以处理,随机选择一个 +* 如果没有通道操作可以处理并且写了`default`语句,它就会执行:`default`永远是可运行的(这就是准备好了,可以执行)。 + +在`select`中使用发送操作并且有`default`可以确保发送不被阻塞!如果没有cases,select就会一直阻塞。 + +`select`语句实现了一种监听模式,通常用在(无限)循环中;在某种情况下,通过`break`语句使循环退出。 + +在程序[goroutine_select.go](examples/chapter_14/goroutine_select.go)中有2个通道`ch1`和`ch2`,三个协程`pump1()`,`pump2()`和`suck()`。这是一个典型的生产者消费者模式。在无限循环中,`ch1`和`ch2`通过`pump1()`和`pump2()`填充整数;`suck()`也是在无限循环中轮询输入的,通过`select`语句获取`ch1`和`ch2`的整数并输出。选择哪一个case取决于哪一个通道收到了信息。程序在main执行1秒后结束。 + +示例 14.10-[goroutine_select.go](examples/chapter_14/goroutine_select.go) +```go +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan int) + ch2 := make(chan int) + + go pump1(ch1) + go pump2(ch2) + go suck(ch1, ch2) + + time.Sleep(1e9) +} + +func pump1(ch chan int) { + for i := 0; ; i++ { + ch <- i * 2 + } +} + +func pump2(ch chan int) { + for i := 0; ; i++ { + ch <- i + 5 + } +} + +func suck(ch1, ch2 chan int) { + for { + select { + case v := <-ch1: + fmt.Printf("Received on channel 1: %d\n", v) + case v := <-ch2: + fmt.Printf("Received on channel 2: %d\n", v) + } + } +} +``` +输出: +``` +Received on channel 2: 5 +Received on channel 2: 6 +Received on channel 1: 0 +Received on channel 2: 7 +Received on channel 2: 8 +Received on channel 2: 9 +Received on channel 2: 10 +Received on channel 1: 2 +Received on channel 2: 11 +... +Received on channel 2: 47404 +Received on channel 1: 94346 +Received on channel 1: 94348 +``` ## 链接 From f419fd486fcbc2d62d20c60033a415686c68d901 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 18:00:08 +0800 Subject: [PATCH 09/21] Update 14.4.md --- eBook/14.4.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/eBook/14.4.md b/eBook/14.4.md index 516b43d..220721d 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -84,6 +84,39 @@ Received on channel 2: 47404 Received on channel 1: 94346 Received on channel 1: 94348 ``` +一秒内的输出非常惊人,如果我们给它计数(goroutine_select2.go),得到了90000个左右的数字。 + +##练习: + +练习14.7: +* a)在练习5.4的for_loop.go中,有一个常见的for循环打印数字。在函数`tel`中实现一个for循环,用协程开始这个函数并在其中给通道发送数字。`main()`线程从通道中获取并打印。不要使用`time.Sleep()`来同步:[goroutine_panic.go](exercises/chapter_14/goroutine_panic.go) +* b)也许你的方案有效,可能会引发运行时的panic:`throw:all goroutines are asleep-deadlock!` 为什么会这样?你如何解决这个问题?[goroutine_close.go]((exercises/chapter_14/goroutine_close.go)) +* c)解决a)的另外一种方式:使用一个额外的通道传递给协程,然后在结束的时候随便放点什么进去。`main()`线程检查是否有数据发送给了这个通道,如果有就停止:[goroutine_select.go](exercises/chapter_14/goroutine_select.go) + + +练习14.8: + +从示例6.10的斐波那契程序开始,制定解决方案,使斐波那契周期计算独立到协程中,并可以把结果发送给通道。 + +结束的时候关闭通道。`main()`函数读取通道并打印结果:[goFibonacci.go](exercises/chapter_14/gofibonacci.go) + +使用练习6.9中的算法写一个更短的[gofibonacci2.go](exercises/chapter_14/gofibonacci2.go) + +使用`select`语句来写,并让通道退出([gofibonacci_select.go](exercises/chapter_14/gofibonacci_select.go)) + +注意:当给结果计时并和6.10对比时,我们发现使用通道通信的性能开销有轻微削减;这个例子中的算法使用协程并非性能最好的选择;但是[gofibonacci3](exercises/chapter_14/gofibonacci3.go)方案使用了2个协程带来了3倍的提速。 + + +练习14.9: + +做一个随机位生成器,程序可以提供无限的随机0或者1的序列:[random_bitgen.go](exercises/chapter_14/random_bitgen.go) + + +练习14.10:[polar_to_cartesian.go](exercises/chapter_14/polar_to_cartesian.go) + +(这是一种综合练习,使用到章节4,9,11的内容和本章内容。) + + ## 链接 From 14b1353816cbfed0c9c8400229111ac83fcf604d Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 18:21:47 +0800 Subject: [PATCH 10/21] Update README_gc.md --- README_gc.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README_gc.md b/README_gc.md index 5c8209c..9626c6f 100644 --- a/README_gc.md +++ b/README_gc.md @@ -37,3 +37,4 @@ Golang 编程:245386165 |2015-12-22|15.4 |2015-12-25|14.1 |2015-12-28|14.2 +|2015-12-30|14.3 14.4 From 1a531b7a1d72faf044225736311c1fe367e53258 Mon Sep 17 00:00:00 2001 From: glight2000 Date: Tue, 29 Dec 2015 18:25:56 +0800 Subject: [PATCH 11/21] add pi series image for chapter 14.4 --- images/14.4_piseries .png | Bin 0 -> 12906 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/14.4_piseries .png diff --git a/images/14.4_piseries .png b/images/14.4_piseries .png new file mode 100644 index 0000000000000000000000000000000000000000..d7c84b6f2ef1bc15cffa4f083ca348f1b29ab1f9 GIT binary patch literal 12906 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 OQIu7ash0W@`hNhW2~)fP literal 0 HcmV?d00001 From 8d05c6d56d318ea4c1948c17b620eade12f7ae55 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 18:28:36 +0800 Subject: [PATCH 12/21] Update 14.4.md --- eBook/14.4.md | 1 - 1 file changed, 1 deletion(-) diff --git a/eBook/14.4.md b/eBook/14.4.md index 220721d..01180d5 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -117,7 +117,6 @@ Received on channel 1: 94348 (这是一种综合练习,使用到章节4,9,11的内容和本章内容。) - ## 链接 - [目录](directory.md) From f0c81a5d04d920598ae3c69e3cf4424dfd535828 Mon Sep 17 00:00:00 2001 From: glight2000 Date: Tue, 29 Dec 2015 18:30:10 +0800 Subject: [PATCH 13/21] rename image file --- images/{14.4_piseries .png => 14.4_piseries.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename images/{14.4_piseries .png => 14.4_piseries.png} (100%) diff --git a/images/14.4_piseries .png b/images/14.4_piseries.png similarity index 100% rename from images/14.4_piseries .png rename to images/14.4_piseries.png From abb36a01a498895610d014639d698ab90cf39dd1 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 29 Dec 2015 23:02:38 +0800 Subject: [PATCH 14/21] Update 14.4.md --- eBook/14.4.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/eBook/14.4.md b/eBook/14.4.md index 01180d5..17b309a 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -114,7 +114,29 @@ Received on channel 1: 94348 练习14.10:[polar_to_cartesian.go](exercises/chapter_14/polar_to_cartesian.go) -(这是一种综合练习,使用到章节4,9,11的内容和本章内容。) +(这是一种综合练习,使用到章节4,9,11的内容和本章内容。)写一个可交互的控制台程序,要求用户输入二位平面极坐标上的点(半径和角度(度))。计算对应的笛卡尔坐标系的点的x和y并输出。使用极坐标和笛卡尔坐标的结构体。 + +使用通道和协程: + `channel1`用来接收极坐标 + `channel2`用来接收笛卡尔坐标 + +转换过程需要在协程中进行,从channel1中读取然后发哦送到channel2。实际上做这种计算不提倡使用协程和通道,但是如果运算量很大很耗时,这种方案设计就非常合适了。 + + +练习14.11: [concurrent_pi.go](exercises/chapter_14/concurrent_pi.go) / [concurrent_pi2.go](exercises/chapter_14/concurrent_pi2.go) + +使用以下序列在协程中计算pi:开启一个协程来计算公式中的每一项并将结果放入通道,`main()`函数收集并累加结果,打印出pi的近似值。 + +![](../images/14.4_piseries.png?raw=true) + +计算执行时间(参见章节[6.11](6.11.md)) + +再次声明这只是为了一边练习协程的概念一边找点乐子。 + +如果你需要的话可使用`math.pi`中的Pi;而且不使用协程会运算的更快。一个急速版本:使用`GOMAXPROCS`,开启和`GOMAXPROCS`同样多个协程。 + +习惯用法:后台服务模式 + ## 链接 From a6aec5bed0302e2477a9bfada26b4fefea1d89ca Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Wed, 30 Dec 2015 17:31:42 +0800 Subject: [PATCH 15/21] =?UTF-8?q?=E5=AE=8C=E6=88=9014.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eBook/14.4.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/eBook/14.4.md b/eBook/14.4.md index 17b309a..392b290 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -137,7 +137,38 @@ Received on channel 1: 94348 习惯用法:后台服务模式 +服务通常是是用后台协程中的无限循环实现的,在循环中使用`select`获取并处理通道中的数据: +```go +// Backend goroutine. +func backend() { + for { + select { + case cmd := <-ch1: + // Handle ... + case cmd := <-ch2: + ... + case cmd := <-chStop: + // stop server + } + } +} +``` +在程序的其他地方给通道`ch1`,`ch2`发送数据,比如:通道`stop`用来清理结束服务程序。 +另一种方式(但是不太灵活)就是(客户端)在`chRequest`上提交请求,后台协程循环这个通道,使用`switch`根据请求的行为来分别处理: +```go +func backent() { + for req := range chRequest { + switch req.Subjext() { + case A1: // Handle case ... + case A2: // Handle case ... + default: + // Handle illegal request .. + // ... + } + } +} +``` ## 链接 From c5b08a6803ca01ed0f25876217857bd426c5675d Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Wed, 30 Dec 2015 22:18:35 +0800 Subject: [PATCH 16/21] =?UTF-8?q?=E5=A2=9E=E5=8A=A014.4=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eBook/directory.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eBook/directory.md b/eBook/directory.md index eac2c1d..70eccbf 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -139,6 +139,7 @@ - 14.1 [并发,并行和协程](14.1.md) - 14.2 [使用通道进行协程间通信](14.2.md) - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) + - 14.4 [使用select切换协程](14.4.md) - 第15章:[网络、模版与网页应用](15.0.md) - 15.1 [tcp服务器](15.1.md) - 15.2 [一个简单的web服务器](15.2.md) From 8fa06effe24b6cc50f71feb52b40ce61a2e49230 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Wed, 30 Dec 2015 22:22:15 +0800 Subject: [PATCH 17/21] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=BB=83=E4=B9=A014.7?= =?UTF-8?q?=E7=9A=84goroutine=5Fclose=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 392b290..4b641a7 100644 --- a/eBook/14.4.md +++ b/eBook/14.4.md @@ -90,7 +90,7 @@ Received on channel 1: 94348 练习14.7: * a)在练习5.4的for_loop.go中,有一个常见的for循环打印数字。在函数`tel`中实现一个for循环,用协程开始这个函数并在其中给通道发送数字。`main()`线程从通道中获取并打印。不要使用`time.Sleep()`来同步:[goroutine_panic.go](exercises/chapter_14/goroutine_panic.go) -* b)也许你的方案有效,可能会引发运行时的panic:`throw:all goroutines are asleep-deadlock!` 为什么会这样?你如何解决这个问题?[goroutine_close.go]((exercises/chapter_14/goroutine_close.go)) +* b)也许你的方案有效,可能会引发运行时的panic:`throw:all goroutines are asleep-deadlock!` 为什么会这样?你如何解决这个问题?[goroutine_close.go](exercises/chapter_14/goroutine_close.go) * c)解决a)的另外一种方式:使用一个额外的通道传递给协程,然后在结束的时候随便放点什么进去。`main()`线程检查是否有数据发送给了这个通道,如果有就停止:[goroutine_select.go](exercises/chapter_14/goroutine_select.go) From 4e18e9fc6223949809c8d2d83952bc31bd9c8f55 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Wed, 30 Dec 2015 22:34:35 +0800 Subject: [PATCH 18/21] Update 14.3.md --- eBook/14.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.3.md b/eBook/14.3.md index 632f25d..cd70a9f 100644 --- a/eBook/14.3.md +++ b/eBook/14.3.md @@ -1,4 +1,4 @@ -# 14.3 协程的同步:关闭通道-测试阻塞的通道 +# 14.4 协程的同步:关闭通道-测试阻塞的通道 通道可以被显示的关闭;尽管它们和文件不同:不必每次都关闭。只有在当需要告诉接收者不会再提供新的值的时候,才需要关闭通道。只有发送者需要关闭通道,接收者永远不会需要。 From db3499ddbce83a65dcaa97b816ff50e7cbd17014 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Wed, 30 Dec 2015 22:34:57 +0800 Subject: [PATCH 19/21] Update 14.3.md --- eBook/14.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/14.3.md b/eBook/14.3.md index cd70a9f..632f25d 100644 --- a/eBook/14.3.md +++ b/eBook/14.3.md @@ -1,4 +1,4 @@ -# 14.4 协程的同步:关闭通道-测试阻塞的通道 +# 14.3 协程的同步:关闭通道-测试阻塞的通道 通道可以被显示的关闭;尽管它们和文件不同:不必每次都关闭。只有在当需要告诉接收者不会再提供新的值的时候,才需要关闭通道。只有发送者需要关闭通道,接收者永远不会需要。 From 70ab9c8d470bbcbee1d235f955564923b12ab397 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Wed, 30 Dec 2015 23:01:37 +0800 Subject: [PATCH 20/21] Create 14.5.md --- eBook/14.5.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 eBook/14.5.md diff --git a/eBook/14.5.md b/eBook/14.5.md new file mode 100644 index 0000000..b01c8a3 --- /dev/null +++ b/eBook/14.5.md @@ -0,0 +1,51 @@ +# 14.5 通道,超时和计时器 + +`time`包中有一些有趣的功能可以和通道组合使用。 + +其中就包含了`time.Ticker`结构体,这个对象以指定的时间间隔重复的向通道C发送时间值: +```go +type Ticker struct { + C <-chan Time // the channel on which the ticks are delivered. + // contains filtered or unexported fields + ... +} +``` +时间间隔的单位是ns(纳秒,int64),在工厂函数`time.NewTicker`中以`Duration`类型的参数传入:`func Newticker(dur) *Ticker` + +在协程周期性的执行一些事情(打印状态日志,输出,计算等等)的时候非常有用。 + +调用`Stop()`使计时器停止,在`defer`语句中使用。这些都很好的适应`select`语句: +```go +ticker := time.NewTicker(updateInterval) +defer ticker.Stop() +... +select { +case u:= <-ch1: + ... +case v:= <-ch2: + ... +case <-ticker.C: + logState(status) // call some logging function logState +default: // no value ready to be received + ... +} +``` +`time.Tick()`函数声明为`Tick(d Duration) <-chan Time`,当你想返回一个通道而不必关闭它的时候这个函数非常有用:它以d为周期给返回的通道发送时间,d是纳秒数。如果需要像下边的代码一样,限制处理频率(函数`client.Call()`是一个RPC调用,这里暂不赘述(参见章节[15.9](15.9.md))): + +```go +import "time" + +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, ...) +} +``` + +## 链接 + +- [目录](directory.md) +- 上一节:[使用select切换协程](14.4.md) +- 下一节:[对协程使用recover](14.6.md) From 8829070b05977298ea1ff273ddbef9711fee1e6c Mon Sep 17 00:00:00 2001 From: Unknwon Date: Thu, 31 Dec 2015 23:53:01 -0500 Subject: [PATCH 21/21] 14.0-14.1 --- README.md | 2 +- README_gc.md | 8 +---- eBook/14.0.md | 6 ++-- eBook/14.1.md | 85 +++++++++++++++++++++++++--------------------- eBook/directory.md | 2 +- 5 files changed, 52 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 759845c..def268b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## 翻译进度 -13.10 [性能调试:分析并优化 Go 程序](eBook/13.10.md) +14.1 [并发、并行和协程](eBook/14.1.md) ## 支持本书 diff --git a/README_gc.md b/README_gc.md index 9626c6f..282c4b1 100644 --- a/README_gc.md +++ b/README_gc.md @@ -31,10 +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.1 并发、并行和协程 diff --git a/eBook/14.0.md b/eBook/14.0.md index bf51abc..ba0d41f 100644 --- a/eBook/14.0.md +++ b/eBook/14.0.md @@ -1,8 +1,8 @@ # 14 协程(goroutine)与通道(channel) -作为一门21世纪的语言,Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算,参见 15 章)和程序的并发。程序可以在不同的处理器和计算机上同时执行不同的代码段。Go 语言为构建并发程序的基本代码块是 协程 (goroutine) 与通道 (channel)。他们需要语言,编译器,和runtime的支持。Go 语言提供的垃圾回收器对并发编程至关重要。 +作为一门 21 世纪的语言,Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算,参见第 15 章)和程序的并发。程序可以在不同的处理器和计算机上同时执行不同的代码段。Go 语言为构建并发程序的基本代码块是 协程 (goroutine) 与通道 (channel)。他们需要语言,编译器,和runtime的支持。Go 语言提供的垃圾回收器对并发编程至关重要。 -*不要通过共享内存来通信,而通过通信来共享内存。* +**不要通过共享内存来通信,而通过通信来共享内存。** 通信强制协作。 @@ -10,4 +10,4 @@ - [目录](directory.md) - 上一节:[性能调试:分析并优化 Go 程序](13.10.md) -- 下一节:[并发,并行和协程](14.1.md) +- 下一节:[并发、并行和协程](14.1.md) diff --git a/eBook/14.1.md b/eBook/14.1.md index 0df3e71..c37e41e 100644 --- a/eBook/14.1.md +++ b/eBook/14.1.md @@ -1,69 +1,69 @@ -# 14.1 并发,并行和协程 +# 14.1 并发、并行和协程 ## 14.1.1 什么是协程 -一个应用程序是运行在机器上的一个进程;进程是一个运行在自己内存地址空间里的独立执行体。一个进程由一个或多个操作系统线程组成,这些线程其实是共享同一个内存地址空间的一起工作的执行体。几乎所有'正式'的程序都是多线程的,以便让用户或计算机不必等待,或者能够同时服务多个请求(如Web服务器),或增加性能和吞吐量(例如,通过对不同的数据集并行执行代码)。一个并发程序可以在一个处理器或者内核上使用多个线程来执行任务,但是只有在同一个程序在某一个时间点在多个些处理内核或处理器上同时执行的任务才是真正的并行。 +一个应用程序是运行在机器上的一个进程;进程是一个运行在自己内存地址空间里的独立执行体。一个进程由一个或多个操作系统线程组成,这些线程其实是共享同一个内存地址空间的一起工作的执行体。几乎所有'正式'的程序都是多线程的,以便让用户或计算机不必等待,或者能够同时服务多个请求(如 Web 服务器),或增加性能和吞吐量(例如,通过对不同的数据集并行执行代码)。一个并发程序可以在一个处理器或者内核上使用多个线程来执行任务,但是只有在同一个程序在某一个时间点在多个些处理内核或处理器上同时执行的任务才是真正的并行。 并行是一种通过使用多处理器以提高速度的能力。所以并发程序可以是并行的,也可以不是。 +公认的,使用多线程的应用难以做到准确,最主要的问题是内存中的数据共享,它们会被多线程以无法预知的方式进行操作,导致一些无法重现或者随机的结果(称作 `竞态`)。 -公认的,使用多线程的应用难以做到准确,最主要的问题是内存中的数据共享,它们会被多线程以无法预知的方式进行操作,导致一些无法重现或者随机的结果(称作`竞态`) +**不要使用全局变量或者共享内存,它们会给你的代码在并发运算的时候带来危险。** -!!不要使用全局变量或者共享内存,它们会给你的代码在并发运算的时候带来危险。 +解决之道在于同步不同的线程,对数据加锁,这样同时就只有一个线程可以变更数据。在 Go 的标准库 `sync` 中有一些工具用来在低级别的代码中实现加锁;我们在第 [9.3](9.3.md) 节中讨论过这个问题。不过过去的软件开发经验告诉我们这会带来更高的复杂度,更容易使代码出错以及更低的性能,所以这个经典的方法明显不再适合现代多核/多处理器编程:`thread-per-connection` 模型不够有效。 -解决之道在于同步不同的线程,对数据加锁,这样同时就只有一个线程可以变更数据。在Go的标准库`sync`中有一些工具用来在低级别的代码中实现加锁;我们在章节[9.3](9.3.md)中讨论过这个问题。不过过去的软件开发经验告诉我们这会带来更高的复杂度,更容易使代码出错以及更低的性能,所以这个经典的方法明显不再适合现代多核/多处理器编程:"`thread-per-connection`"模型不够有效。 +Go 更倾向于其他的方式,在诸多比较合适的范式中,有个被称作 `Communicating Sequential Processes(顺序通信处理)`(CSP, C. Hoare 发明的)还有一个叫做 `message passing-model(消息传递)`(已经运用在了其他语言中,比如 Eralng)。 -Go更倾向于其他的方式,在诸多比较合适的范式中,有个被称作`Communicating Sequential Processes(顺序通信处理)`(CSP, C. Hoare发明的)还有一个叫做`message passing-model(消息传递)`(已经运用在了其他语言中,比如Eralng)。 +在 Go 中,应用程序并发处理的部分被称作 `goroutines(协程)`,它可以进行更有效的并发运算。在协程和操作系统线程之间并无一对一的关系:协程是根据一个或多个线程的可用性,映射(多路复用,执行于)在他们之上的;协程调度器在 Go 运行时很好的完成了这个工作。 -在Go中,应用程序并发处理的部分被称作`goroutines(协程)`,它可以进行更有效的并发运算。在协程和操作系统线程之间并无一对一的关系:协程是根据一个或多个线程的可用性,映射(多路复用,执行于)在他们之上的;协程调度器在Go运行时很好的完成了这个工作。 +协程工作在相同的地址空间中,所以共享内存的方式一定是同步的;这个可以使用 `sync` 包来实现(参见第 [9.3](9.3.md) 节),不过我们很不鼓励这样做:Go 使用 `channels` 来同步协程(可以参见第 [14.2](14.2.md) 节等章节) -协程工作在相同的地址空间中,所以共享内存的方式一定是同步的;这个可以使用`sync`包来实现(参见章节[9.3](9.3.md)),不过我们很不鼓励这样做:Go使用`channels`来同步协程(可以参见[14.2](14.2.md)等章节) +当系统调用(比如等待 I/O)阻塞协程时,其他协程会继续在其他线程上工作。协程的设计隐藏了许多线程创建和管理方面的复杂工作。 -当系统调用(比如等待I/O)阻塞协程时,其他协程会继续在其他线程上工作。协程的设计隐藏了许多线程创建和管理方面的复杂工作。 +协程是轻量的,比线程更轻。它们痕迹非常不明显(使用少量的内存和资源):使用 4K 的栈内存就可以在堆中创建它们。因为创建非常廉价,必要的时候可以轻松创建并运行大量的协程(在同一个一个地址空间中 100,000 个连续的协程)。并且它们对栈进行了分割,从而动态的增加(或缩减)内存的使用;栈的管理是自动的,但不是由垃圾回收器管理的,而是在协程退出后自动释放。 -协程是轻量的,比线程更轻。它们痕迹非常不明显(使用少量的内存和资源):使用4K的栈内存就可以在堆中创建它们。因为创建非常廉价,必要的时候可以轻松创建并运行大量的协程(在同一个一个地址空间中100,000个连续的协程)。并且它们对栈进行了分割,从而动态的增加(或缩减)内存的使用;栈的管理是自动的,但不是由垃圾回收器管理的,而是在协程退出后自动释放。 +协程可以运行在多个操作系统线程之间,也可以运行在线程之内,让你可以很小的内存占用就可以处理大量的任务。由于操作系统线程上的协程时间片,你可以使用少量的操作系统线程就能拥有任意多个提供服务的协程,而且 Go 运行时可以聪明的意识到哪些协程被阻塞了,暂时搁置它们并处理其他协程。 -协程可以运行在多个操作系统线程之间,也可以运行在线程之内,让你可以很小的内存占用就可以处理大量的任务。由于操作系统线程上的协程时间片,你可以使用少量的操作系统线程就能拥有任意多个提供服务的协程,而且Go运行时可以聪明的意识到哪些协程被阻塞了,暂时搁置它们并处理其他协程。 +存在两种并发方式:确定性的(明确定义排序)和非确定性的(加锁/互斥从而未定义排序)。Go 的协程和通道理所当然的支持确定性的并发方式(例如通道具有一个 sender 和一个 receiver)。我们会在第 [14.7](14.7.md) 节中使用一个常见的算法问题(工人问题)来对比两种处理方式。 -存在两种并发方式:确定性的(明确定义排序)和非确定性的(加锁/互斥从而未定义排序)。Go的协程和通道理所当然的支持确定性的并发方式(例如通道具有一个sender和一个receiver)。我们会在章节[14.7](14.7.md)中使用一个常见的算法问题(工人问题)来对比两种处理方式。 - -协程是通过使用关键字`go`调用(执行)一个函数或者方法来实现的(也可以是匿名或者lambda函数)。这样会在当前的计算过程中开始一个同时进行的函数,在相同的地址空间中并且分配了独立的栈,比如:`go sum(bigArray)`// 在后台计算总和 +协程是通过使用关键字 `go` 调用(执行)一个函数或者方法来实现的(也可以是匿名或者 lambda 函数)。这样会在当前的计算过程中开始一个同时进行的函数,在相同的地址空间中并且分配了独立的栈,比如:`go sum(bigArray)`,在后台计算总和。 协程的栈会根据需要进行伸缩,不出现栈溢出;开发者不需要关心栈的大小。当协程结束的时候,它会静默退出:用来启动这个协程的函数不会得到任何的返回值。 -任何Go程序都必须有的`main()`函数也可以看做是一个协程,尽管它并没有通过`go`来启动。协程可以在程序初始化的过程中运行(在`init()`函数中)。 +任何 Go 程序都必须有的 `main()` 函数也可以看做是一个协程,尽管它并没有通过 `go` 来启动。协程可以在程序初始化的过程中运行(在 `init()` 函数中)。 -在一个协程中,比如它需要进行非常密集的运算,你可以在运算循环中周期的使用`runtime.Gosched()`:这会让出处理器,允许运行其他协程;它并不会使当前协程挂起,所以它会自动恢复执行。使用`Gosched()`可以使计算均匀分布,使通信不至于迟迟得不到响应。 +在一个协程中,比如它需要进行非常密集的运算,你可以在运算循环中周期的使用 `runtime.Gosched()`:这会让出处理器,允许运行其他协程;它并不会使当前协程挂起,所以它会自动恢复执行。使用 `Gosched()` 可以使计算均匀分布,使通信不至于迟迟得不到响应。 ## 14.1.2 并发和并行的差异 -Go的并发原语提供了良好的并发设计基础:表达程序结构以便表示独立地执行的动作;所以Go的的重点不在于并行的首要位置:并发程序可能是并行的,也可能不是。并行是一种通过使用多处理器以提高速度的能力。但往往是,一个设计良好的并发程序在并行方面的表现也非常出色。 +Go 的并发原语提供了良好的并发设计基础:表达程序结构以便表示独立地执行的动作;所以Go的的重点不在于并行的首要位置:并发程序可能是并行的,也可能不是。并行是一种通过使用多处理器以提高速度的能力。但往往是,一个设计良好的并发程序在并行方面的表现也非常出色。 -在当前的运行时(2012年一月)实现中,Go默认没有并行指令,只有一个独立的核心或处理器被专门用于Go程序,不论它启动了多少个协程;所以这些协程是并发运行的,但他们不是并行运行的:同一时间只有一个协程会处在运行状态。 +在当前的运行时(2012 年一月)实现中,Go 默认没有并行指令,只有一个独立的核心或处理器被专门用于 Go 程序,不论它启动了多少个协程;所以这些协程是并发运行的,但他们不是并行运行的:同一时间只有一个协程会处在运行状态。 -这个情况在以后可能会发生改变,不过届时,为了使你的程序可以使用多个核心运行,这时协程就真正的是并行运行了,你必须使用`GOMAXPROCS`变量。 +这个情况在以后可能会发生改变,不过届时,为了使你的程序可以使用多个核心运行,这时协程就真正的是并行运行了,你必须使用 `GOMAXPROCS` 变量。 这会告诉运行时有多少个协程同时执行。 -并且只有gc编译器真正实现了协程,适当的把协程映射到操作系统线程。使用`gccgo`编译器,会为每一个协程创建操作系统线程。 +并且只有 gc 编译器真正实现了协程,适当的把协程映射到操作系统线程。使用 `gccgo` 编译器,会为每一个协程创建操作系统线程。 -## 14.1.3 使用GOMAXPROCS +## 14.1.3 使用 GOMAXPROCS -在gc编译器下(6g或者8g)你必须设置GOMAXPROCS为一个大于默认值1的数值来允许运行时支持使用多于1个的操作系统线程,所有的协程都会共享同一个线程除非将GOMAXPROCS设置为一个大于1的数。当GOMAXPROCS大于1时,会有一个线程池管理许多的线程。通过`gccgo`编译器GOMAXPROCS有效的与运行中的协程数量相等。假设n是机器上处理器或者核心的数量。如果你设置环境变量GOMAXPROCS>=n,或者执行`runtime.GOMAXPROCS(n)`,接下来协程会被分割(分散)到n个处理器上。更多的处理器并不意味着性能的线性提升。有这样一个经验法则,对于n个核心的情况设置GOMAXPROCS为n-1以获得最佳性能,也同样需要遵守这条规则:协程的数量 > 1 + GOMAXPROCS > 1 +在 gc 编译器下(6g 或者 8g)你必须设置 GOMAXPROCS 为一个大于默认值 1 的数值来允许运行时支持使用多于 1 个的操作系统线程,所有的协程都会共享同一个线程除非将 GOMAXPROCS 设置为一个大于 1 的数。当 GOMAXPROCS 大于 1 时,会有一个线程池管理许多的线程。通过 `gccgo` 编译器 GOMAXPROCS 有效的与运行中的协程数量相等。假设 n 是机器上处理器或者核心的数量。如果你设置环境变量 GOMAXPROCS>=n,或者执行 `runtime.GOMAXPROCS(n)`,接下来协程会被分割(分散)到 n 个处理器上。更多的处理器并不意味着性能的线性提升。有这样一个经验法则,对于 n 个核心的情况设置 GOMAXPROCS 为 n-1 以获得最佳性能,也同样需要遵守这条规则:协程的数量 > 1 + GOMAXPROCS > 1。 -所以如果在某一时间只有一个协程在执行,不要设置GOMAXPROCS! +所以如果在某一时间只有一个协程在执行,不要设置 GOMAXPROCS! -还有一些通过实验观察到的现象:在一台1颗CPU的笔记本电脑上,增加GOMAXPROCS到9会带来性能提升。在一台32核的机器上,设置GOMAXPROCS=8会达到最好的性能,在测试环境中,更高的数值无法提升性能。如果设置一个很大的GOMAXPROCS只会带来轻微的性能下降;设置GOMAXPROCS=100,使用“top”命令和“H”选项查看到只有7个活动的线程。 +还有一些通过实验观察到的现象:在一台 1 颗 CPU 的笔记本电脑上,增加 GOMAXPROCS 到 9 会带来性能提升。在一台 32 核的机器上,设置 GOMAXPROCS=8 会达到最好的性能,在测试环境中,更高的数值无法提升性能。如果设置一个很大的 GOMAXPROCS 只会带来轻微的性能下降;设置 GOMAXPROCS=100,使用 `top` 命令和 `H` 选项查看到只有 7 个活动的线程。 -增加GOMAXPROCS的数值对程序进行并发计算是有好处的; +增加 GOMAXPROCS 的数值对程序进行并发计算是有好处的; -请看[goroutine_select2.go](examples/chapter_14/goroutine_select2.go) +请看 [goroutine_select2.go](examples/chapter_14/goroutine_select2.go) -总结:GOMAXPROCS等同于(并发的)线程数凉,在一台核心数多于1个的机器上,会尽可能有等同于核心数的线程在并行运行。 +总结:GOMAXPROCS 等同于(并发的)线程数凉,在一台核心数多于1个的机器上,会尽可能有等同于核心数的线程在并行运行。 ## 14.1.4 如何用命令行指定使用的核心数量 -使用`flags`包,如下: +使用 `flags` 包,如下: + ```go var numCores = flag.Int("n", 2, "number of CPU cores to use") @@ -75,6 +75,7 @@ runtime.GOMAXPROCS(*numCores) 协程可以通过调用`runtime.Goexit()`来停止,尽管这样做几乎没有必要。 示例 14.1-[goroutine1.go](examples/chapter_14/goroutine1.go) 介绍了概念: + ```go package main @@ -105,7 +106,9 @@ func shortWait() { fmt.Println("End of shortWait()") } ``` -输出 + +输出: + ``` In main() About to sleep in main() @@ -115,17 +118,19 @@ End of shortWait() End of longWait() At the end of main() // after 10s ``` -`main()`,`longWait()`和`shortWait()`三个函数作为独立的处理单元按顺序启动,然后开始并行运行。每一个函数都在运行的开始和结束阶段输出了消息。为了模拟他们运算的时间消耗,我们使用了`time`包中的`Sleep`函数。`Sleep()`可以按照指定的时间来暂停函数或协程的执行,这里使用了纳秒(ns,符号1e9表示1乘10的9次方,e=指数)。 -他们按照我们期望的顺序打印出了消息,几乎都一样,可是我们明白这是模拟出来的,以并行的方式。我们让`main()`函数暂停10秒从而确定它会在另外两个协程之后结束。如果不这样(如果我们让`main()`函数停止4秒),`main()`会提前结束,`longWait()`则无法完成。如果我们不在`main()`中等待,协程会随着程序的结束而消亡。 +`main()`,`longWait()` 和 `shortWait()` 三个函数作为独立的处理单元按顺序启动,然后开始并行运行。每一个函数都在运行的开始和结束阶段输出了消息。为了模拟他们运算的时间消耗,我们使用了 `time` 包中的 `Sleep` 函数。`Sleep()` 可以按照指定的时间来暂停函数或协程的执行,这里使用了纳秒(ns,符号 1e9 表示 1 乘 10 的 9 次方,e=指数)。 -当`main()`函数返回的时候,程序退出:它不会等待任何其他非main协程的结束。这就是为什么在服务器程序中,每一个请求都会启动一个协程来处理,`server()`函数必须保持运行状态。通常使用一个无限循环来达到这样的目的。 +他们按照我们期望的顺序打印出了消息,几乎都一样,可是我们明白这是模拟出来的,以并行的方式。我们让 `main()` 函数暂停 10 秒从而确定它会在另外两个协程之后结束。如果不这样(如果我们让 `main()` 函数停止 4 秒),`main()` 会提前结束,`longWait()` 则无法完成。如果我们不在 `main()` 中等待,协程会随着程序的结束而消亡。 + +当 `main()` 函数返回的时候,程序退出:它不会等待任何其他非 main 协程的结束。这就是为什么在服务器程序中,每一个请求都会启动一个协程来处理,`server()` 函数必须保持运行状态。通常使用一个无限循环来达到这样的目的。 另外,协程是独立的处理单元,一旦陆续启动一些协程,你无法确定他们是什么时候真正开始执行的。你的代码罗技必须具独立于协程调用的顺序。 -为了对比使用一个线程,连续调用的情况,移除go关键字,重新运行程序。 +为了对比使用一个线程,连续调用的情况,移除 go 关键字,重新运行程序。 现在输出: + ``` In main() Beginning longWait() @@ -135,19 +140,21 @@ End of shortWait() About to sleep in main() At the end of main() // after 17 s ``` + 协程更有用的一个例子应该是在一个非常长的数组中查找一个元素。 将数组分割为若干个不重复的切片,然后给每一个切片启动一个协程进行查找计算。这样许多并行的线程可以用来进行查找任务,整体的查找时间会缩短(除以协程的数量)。 -## 14.1.5 Go协程(goroutines)和协程(coroutines) +## 14.1.5 Go 协程(goroutines)和协程(coroutines) -(译者注:标题中的“Go协程(goroutines)”即是14章讲的协程指的是go语言中的协程。而“协程(coroutines)”指的是其他语言中的协程概念,仅在本节出现。) +(译者注:标题中的“Go协程(goroutines)” 即是 14 章讲的协程指的是 Go 语言中的协程。而“协程(coroutines)”指的是其他语言中的协程概念,仅在本节出现。) -在其他语言中,比如C#,Lua或者Python都有协程的概念。这个名字表明它和Go协程有些相似,不过有两点不同: -* go协程意味着并行(或者可以以并行的方式部署),协程一般来说不是这样的 -* go协程通过通道来通信;协程通过让出和恢复操作来通信 +在其他语言中,比如 C#,Lua 或者 Python 都有协程的概念。这个名字表明它和 G o协程有些相似,不过有两点不同: -Go协程比协程更强大,也很容易从协程的逻辑复用到go协程。 +- Go 协程意味着并行(或者可以以并行的方式部署),协程一般来说不是这样的 +- Go 协程通过通道来通信;协程通过让出和恢复操作来通信 + +Go 协程比协程更强大,也很容易从协程的逻辑复用到 Go 协程。 ## 链接 diff --git a/eBook/directory.md b/eBook/directory.md index 70eccbf..041c98b 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -136,7 +136,7 @@ - 13.9 [用(测试数据)表驱动测试](13.9.md) - 13.10 [性能调试:分析并优化 Go 程序](13.10.md) - 第14章:[协程(goroutine)与通道(channel)](14.0.md) - - 14.1 [并发,并行和协程](14.1.md) + - 14.1 [并发、并行和协程](14.1.md) - 14.2 [使用通道进行协程间通信](14.2.md) - 14.3 [协程同步:关闭通道-对阻塞的通道进行测试](14.3.md) - 14.4 [使用select切换协程](14.4.md)