Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
glight2000 committed Dec 29, 2015
2 parents 1a531b7 + 14b1353 commit cd5ee5a
Show file tree
Hide file tree
Showing 4 changed files with 405 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README_gc.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ Golang 编程:245386165
|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
167 changes: 167 additions & 0 deletions eBook/14.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,175 @@ func (c *container) Iter () <- chan items {
return ch
}
```
在协程里,一个for循环迭代容器c中的元素(对于树或图的算法,这种简单的for循环可以替换为深度优先搜索)。

调用这个方法的代码可以这样迭代容器:
```go
for x := range container.Iter() { ... }
```
可以运行在自己的协程中,所以上边的迭代用到了一个通道和两个协程(可能运行在两个线程上)。就有了一个特殊的生产者-消费者模式。如果程序在协程给通道写完值之前结束,协程不会被回收;设计如此。这种行为看起来是错误的,但是通道是一种线程安全的通信。在这种情况下,协程尝试写入一个通道,而这个通道永远不会被读取,这可能是个bug而并非期望它被静默的回收。

习惯用法:生产者消费者模式

假设你有`Produce()`函数来产生`Consume`函数需要的值。它们都可以运行在独立的协程中,生产者在通道中放入给消费者读取的值。整个处理过程可以替换为无限循环:
```go
for {
Consume(Produce())
}
```

## 14.2.11 通道的方向

通道类型可以用注解来表示它只发送或者只接收:
```go
var send_only chan<- int // channel can only receive data
var recv_only <-chan int // channel can onley send data
```
只接收的通道(<-chan T)无法关闭,因为关闭通道是发送者用来表示不再给通道发送值了,所以对只接收通道是没有意义的。通道创建的时候都是双向的,但也可以分配有方向的通道变量,就像以下代码:
```go
var c = make(chan int) // bidirectional
go source(c)
go sink(c)

func source(ch chan<- int){
for { ch <- 1 }
}

func sink(ch <-chan int) {
for { <-ch }
}
```

习惯用法:管道和选择器模式

更具体的例子还有协程处理它从通道接收的数据并发送给输出通道:
```go
sendChan := make(chan int)
reciveChan := make(chan string)
go processChannel(sendChan, receiveChan)

func processChannel(in <-chan int, out chan<- string) {
for inValue := range in {
result := ... /// processing inValue
out <- result
}
}
```
通过使用方向注解来限制协程对通道的操作。

这里有一个来自Go指导的很赞的例子,打印了输出的素数,使用选择器(‘筛’)作为它的算法。每个prime都有一个选择器,如下图:

![](../images/14.2_fig14.2.png?raw=true)

版本1: 示例 14.7-[sieve1.go](examples/chapter_14/sieve1.go)
```go
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.package main
package main

import "fmt"

// Send the sequence 2, 3, 4, ... to channel 'ch'.
func generate(ch chan int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
}
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func filter(in, out chan int, prime int) {
for {
i := <-in // Receive value of new variable 'i' from 'in'.
if i%prime != 0 {
out <- i // Send 'i' to channel 'out'.
}
}
}

// The prime sieve: Daisy-chain filter processes together.
func main() {
ch := make(chan int) // Create a new channel.
go generate(ch) // Start generate() as a goroutine.
for {
prime := <-ch
fmt.Print(prime, " ")
ch1 := make(chan int)
go filter(ch, ch1, prime)
ch = ch1
}
}
```
协程`filter(in, out chan int, prime int)`拷贝整数到输出通道,丢弃掉可以被prime整除的数字。然后每个prime又开启了一个新的协程,生成器和选择器并发请求。
```
输出:2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101
103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223
227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349
353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479
487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619
631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769
773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929
937 941 947 953 967 971 977 983 991 997 1009 1013...
```
第二个版本引入了上边的习惯用法:函数`sieve`,`generate`,和`filter`都是工厂;它们创建通道并返回,而且使用了协程的lambda函数。`main`函数现在短小清晰:它调用`sieve()`返回了包含素数的通道,然后通过`fmt.Println(<-primes)`打印出来。

版本2:示例 14.8-[sieve2.go](examples/chapter_14/sieve2.go)
```go
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"fmt"
)

// Send the sequence 2, 3, 4, ... to returned channel
func generate() chan int {
ch := make(chan int)
go func() {
for i := 2; ; i++ {
ch <- i
}
}()
return ch
}

// Filter out input values divisible by 'prime', send rest to returned channel
func filter(in chan int, prime int) chan int {
out := make(chan int)
go func() {
for {
if i := <-in; i%prime != 0 {
out <- i
}
}
}()
return out
}

func sieve() chan int {
out := make(chan int)
go func() {
ch := generate()
for {
prime := <-ch
ch = filter(ch, prime)
out <- prime
}
}()
return out
}

func main() {
primes := sieve()
for {
fmt.Println(<-primes)
}
}
```

## 链接

Expand Down
110 changes: 110 additions & 0 deletions eBook/14.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 14.3 协程的同步:关闭通道-测试阻塞的通道

通道可以被显示的关闭;尽管它们和文件不同:不必每次都关闭。只有在当需要告诉接收者不会再提供新的值的时候,才需要关闭通道。只有发送者需要关闭通道,接收者永远不会需要。

继续看示例[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)
}
```
阻塞和生产者-消费者模式:

在章节14.2.10的通道迭代器中,两个协程经常是一个阻塞另外一个。如果程序工作在多核心的机器上,大部分时间只用到了一个处理器。可以通过使用带缓冲(缓冲空间大于0)的通道来改善。比如,缓冲大小为100,迭代器在阻塞之前,至少可以从容器获得100个元素。如果消费者协程在独立的内核运行,就有可能让协程不会出现阻塞。

由于容器中元素的数量通常是已知的,需要让通道有足够的容量放置所有的元素。这样,迭代器就不会阻塞(尽管消费者协程仍然可能阻塞)。然后,这样有效的加倍了迭代容器所需要的内存使用量,所以通道的容量需要限制一下最大值。记录运行时间和性能测试可以帮助你找到最小的缓存容量带来最好的性能。

## 链接

- [目录](directory.md)
- 上一节:[协程间的信道](14.2.md)
- 下一节:[使用select切换协程](14.4.md)
Loading

0 comments on commit cd5ee5a

Please sign in to comment.