Update 14.2.md

This commit is contained in:
glight2000
2015-12-25 22:07:13 +08:00
parent 87184288ee
commit 877d5f37bf

View File

@@ -49,8 +49,161 @@ if <- ch != 1000{
...
}
```
操作符 <- 也被用来发送和接收Go尽管不必要为了可读性通道的命名通常以`ch`开头或者包含`chan`通道的发送和接收操作都是自动的它们通常一气呵成下面的示例展示了通信操作
示例 14.2-[goroutine2.go](examples/chapter_14/goroutine2.go)
```go
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go sendData(ch)
go getData(ch)
time.Sleep(1e9)
}
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokio"
}
func getData(ch chan string) {
var input string
// time.Sleep(1e9)
for {
input = <-ch
fmt.Printf("%s ", input)
}
}
```
输出
```
Washington Tripoli London Beijing Tokio
```
`main()`函数中启动了两个协程`sendData()`通过通道ch发送了5个字符串`getData()`按顺序接收它们并打印出来
如果2个协程需要通信你必须给他们同一个通道作为参数才行
尝试一下如果注释掉`time.Sleep(1e9)`会如何
我们发现协程之间的同步非常重要
* main()等待了1秒让两个协程完成如果不这样sendData()就没有机会输出
* getData()使用了无限循环它随着sendData()的发送完成和ch变空也结束了
* 如果我们移除一个或所有go关键字程序无法运行Go运行时会抛出panic
```
---- Error run E:/Go/Goboek/code examples/chapter 14/goroutine2.exe with code Crashed ---- Program exited with code -2147483645: panic: all goroutines are asleep-deadlock!
```
为什么会这样运行时会检查所有的协程也许只有一个是这种情况是否在等待可以读取或者写入某个通道意味着程序无法处理这是死锁deadlock形式运行时可以检测到这种情况
注意不要使用打印状态来表明通道的发送和接收顺序由于打印状态和通道实际发生读写的时间延迟会导致和真实发生的顺序不同
练习 14.4解释一下为什么如果在函数`getData()`的一开始插入`time.Sleep(1e9)`不会出现错误但也没有输出呢
## 14.2.3 通道阻塞
默认情况下通信是同步且无缓冲的在有接受者接收数据之前发送不会结束可以想象一个无缓冲的通道在没有空间来保存数据的时候必须要一个接收者准备好接收通道的数据然后发送者可以直接把数据发送给接收者所以通道的发送/接收操作在对方准备好之前是阻塞的
1对于同一个通道发送操作协程或者函数中的在接收者准备好之前是阻塞的如果ch中的数据无人接收就无法再给通道传入其他数据新的输入无法在通道非空的情况下传入所以发送操作会等待ch再次变为可用状态就是通道值被接收时可以传入变量)。
2对于同一个通道接收操作是阻塞的协程或函数中的直到发送者可用如果通道中没有数据接收者就阻塞了
尽管这看上去是非常严格的约束实际在大部分情况下工作的很不错
程序`channel_block.go`验证了以上理论一个协程在无限循环中给通道发送整数数据不过因为没有接收者只输出了一个数字0
示例 14.3-[channel_block.go](examples/chapter_14/channel_block.go)
```go
package main
import "fmt"
func main() {
ch1 := make(chan int)
go pump(ch1) // pump hangs
fmt.Println(<-ch1) // prints only 0
}
func pump(ch chan int) {
for i := 0; ; i++ {
ch <- i
}
}
```
输出
```
0
```
`pump()`函数为通道提供数值也被叫做生产者
为通道解除阻塞定义了`suck`函数来在无限循环中读取通道参见示例 14.4-[channel_block2.go](examples/chapter_14/channel_block2.go)
```go
func suck(ch chan int) {
for {
fmt.Println(<-ch)
}
}
```
`main()`中使用协程开始它
```go
go pump(ch1)
go suck(ch1)
time.Sleep(1e9)
```
给程序1秒的时间来运行输出了上万个整数
练习 14.1[channel_block3.go](exercises/chapter_14/channel_block3.go)写一个通道证明它的阻塞性开启一个协程接收通道的数据持续15秒然后给通道放入一个值在不同的阶段打印消息并观察输出
## 14.2.4 通过一个(或多个)通道交换数据进行协程同步。
通信是一种同步形式通过通道两个协程在通信协程会和中某刻同步交换数据无缓冲通道成为了多个协程同步的完美工具
甚至可以在通道两端互相阻塞对方形成了叫做死锁的状态Go运行时会检查并panic停止程序死锁几乎完全是由糟糕的设计导致的
无缓冲通道会被阻塞设计无阻塞的程序可以避免这种情况或者使用带缓冲的通道
练习 14.2 [blocking.go](exercises/chapter_14/blocking.go)
解释为什么下边这个程序会导致panic所有的协程都休眠了 - 死锁
```go
package main
import (
"fmt"
)
func f1(in chan int) {
fmt.Println(<-in)
}
func main() {
out := make(chan int)
out <- 2
go f1(out)
}
```
## 14.2.5 同步通道-使用带缓冲的通道
一个无缓冲通道只能包含1个元素有时显得很局限我们给通道提供了一个缓存可以在扩展的`make`命令中设置它的容量如下
```go
buf := 100
ch1 := make(chan string, buf)
```
buf是通道可以承受的元素这里是string个数
在缓冲满载缓冲被全部使用之前给一个带缓冲的通道发送数据是不会阻塞的而从通道读取数据也不会阻塞直到缓冲空了
缓冲容量和类型无关所以可以尽管可能导致危险给一些通道设置不同的容量只要他们拥有同样的元素类型