mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-11-13 09:16:10 +08:00
修改部分描述,添加必要的标点符号,补充代码超链接 (#804)
This commit is contained in:
@@ -14,9 +14,9 @@
|
||||
|
||||
通常使用这样的格式来声明通道:`var identifier chan datatype`
|
||||
|
||||
未初始化的通道的值是nil。
|
||||
未初始化的通道的值是 nil 。
|
||||
|
||||
所以通道只能传输一种类型的数据,比如 `chan int` 或者 `chan string`,所有的类型都可以用于通道,空接口 `interface{}` 也可以。甚至可以(有时非常有用)创建通道的通道。
|
||||
所以通道只能传输一种类型的数据,比如 `chan int` 或者 `chan string`,所有的类型都可以用于通道,空接口 `interface{}` 也可以,甚至可以(有时非常有用)创建通道的通道。
|
||||
|
||||
通道实际上是类型化消息的队列:使数据得以传输。它是先进先出(FIFO)的结构所以可以保证发送给他们的元素的顺序(有些人知道,通道可以比作 Unix shells 中的双向管道(two-way pipe))。通道也是引用类型,所以我们使用 `make()` 函数来给它分配内存。这里先声明了一个字符串通道 ch1,然后创建了它(实例化):
|
||||
|
||||
@@ -27,7 +27,7 @@ ch1 = make(chan string)
|
||||
|
||||
当然可以更短: `ch1 := make(chan string)`。
|
||||
|
||||
这里我们构建一个int通道的通道: `chanOfChans := make(chan chan int)`。
|
||||
这里我们构建一个 int 通道的通道: `chanOfChans := make(chan chan int)`。
|
||||
|
||||
或者函数通道:`funcChan := make(chan func())`(相关示例请看第 [14.17](14.17.md) 节)。
|
||||
|
||||
@@ -53,7 +53,7 @@ if <- ch != 1000{
|
||||
}
|
||||
```
|
||||
|
||||
同一个操作符 `<-` 既用于**发送**也用于**接收**,但Go会根据操作对象弄明白该干什么 。虽非强制要求,但为了可读性通道的命名通常以 `ch` 开头或者包含 `chan`。通道的发送和接收都是原子操作:它们总是互不干扰的完成的。下面的示例展示了通信操作符的使用。
|
||||
同一个操作符 `<-` 既用于**发送**也用于**接收**,但 Go 会根据操作对象弄明白该干什么 。虽非强制要求,但为了可读性通道的命名通常以 `ch` 开头或者包含 `chan` 。通道的发送和接收都是原子操作:它们总是互不干扰地完成。下面的示例展示了通信操作符的使用。
|
||||
|
||||
示例 14.2-[goroutine2.go](examples/chapter_14/goroutine2.go)
|
||||
|
||||
@@ -124,13 +124,13 @@ Washington Tripoli London Beijing tokyo
|
||||
|
||||
默认情况下,通信是同步且无缓冲的:在有接受者接收数据之前,发送不会结束。可以想象一个无缓冲的通道在没有空间来保存数据的时候:必须要一个接收者准备好接收通道的数据然后发送者可以直接把数据发送给接收者。所以通道的发送/接收操作在对方准备好之前是阻塞的:
|
||||
|
||||
1)对于同一个通道,发送操作(协程或者函数中的),在接收者准备好之前是阻塞的:如果ch中的数据无人接收,就无法再给通道传入其他数据:新的输入无法在通道非空的情况下传入。所以发送操作会等待 ch 再次变为可用状态:就是通道值被接收时(可以传入变量)。
|
||||
1)对于同一个通道,发送操作(协程或者函数中的),在接收者准备好之前是阻塞的:如果 ch 中的数据无人接收,就无法再给通道传入其他数据:新的输入无法在通道非空的情况下传入。所以发送操作会等待 ch 再次变为可用状态:就是通道值被接收时(可以传入变量)。
|
||||
|
||||
2)对于同一个通道,接收操作是阻塞的(协程或函数中的),直到发送者可用:如果通道中没有数据,接收者就阻塞了。
|
||||
|
||||
尽管这看上去是非常严格的约束,实际在大部分情况下工作的很不错。
|
||||
|
||||
程序 `channel_block.go` 验证了以上理论,一个协程在无限循环中给通道发送整数数据。不过因为没有接收者,只输出了一个数字 0。
|
||||
程序 [channel_block.go](examples/chapter_14/channel_block.go) 验证了以上理论,一个协程在无限循环中给通道发送整数数据。不过因为没有接收者,只输出了一个数字 0。
|
||||
|
||||
示例 14.3-[channel_block.go](examples/chapter_14/channel_block.go)
|
||||
|
||||
@@ -226,12 +226,12 @@ buf 是通道可以同时容纳的元素(这里是 string)个数
|
||||
|
||||
缓冲容量和类型无关,所以可以(尽管可能导致危险)给一些通道设置不同的容量,只要他们拥有同样的元素类型。内置的 `cap` 函数可以返回缓冲区的容量。
|
||||
|
||||
如果容量大于 0,通道就是异步的了:缓冲满载(发送)或变空(接收)之前通信不会阻塞,元素会按照发送的顺序被接收。如果容量是0或者未设置,通信仅在收发双方准备好的情况下才可以成功。
|
||||
如果容量大于 0,通道就是异步的了:缓冲满载(发送)或变空(接收)之前通信不会阻塞,元素会按照发送的顺序被接收。如果容量是 0 或者未设置,通信仅在收发双方准备好的情况下才可以成功。
|
||||
|
||||
同步:`ch :=make(chan type, value)`
|
||||
|
||||
- value == 0 -> synchronous, unbuffered (阻塞)
|
||||
- value > 0 -> asynchronous, buffered(非阻塞)取决于value元素
|
||||
- value > 0 -> asynchronous, buffered(非阻塞)取决于 value 元素
|
||||
|
||||
若使用通道的缓冲,你的程序会在“请求”激增的时候表现更好:更具弹性,专业术语叫:更具有伸缩性(scalable)。在设计算法时首先考虑使用无缓冲通道,只在不确定的情况下使用缓冲。
|
||||
|
||||
@@ -285,7 +285,7 @@ doSomethingElseForAWhile()
|
||||
<- ch // Wait for goroutine to finish; discard sent value.
|
||||
```
|
||||
|
||||
或者等待两个协程完成,每一个都会对切片s的一部分进行排序,片段如下:
|
||||
或者等待两个协程完成,每一个都会对切片 s 的一部分进行排序,片段如下:
|
||||
|
||||
```go
|
||||
done := make(chan bool)
|
||||
@@ -301,7 +301,7 @@ go doSort(s[i:])
|
||||
<-done
|
||||
```
|
||||
|
||||
下边的代码,用完整的信号量模式对长度为N的 float64 切片进行了 N 个` doSomething()` 计算并同时完成,通道 sem 分配了相同的长度(且包含空接口类型的元素),待所有的计算都完成后,发送信号(通过放入值)。在循环中从通道 sem 不停的接收数据来等待所有的协程完成。
|
||||
下边的代码,用完整的信号量模式对长度为 N 的 float64 切片进行了 N 个 `doSomething()` 计算并同时完成,通道 sem 分配了相同的长度(且包含空接口类型的元素),待所有的计算都完成后,发送信号(通过放入值)。在循环中从通道 sem 不停的接收数据来等待所有的协程完成。
|
||||
|
||||
```go
|
||||
type Empty interface {}
|
||||
@@ -321,7 +321,7 @@ for i, xi := range data {
|
||||
for i := 0; i < N; i++ { <-sem }
|
||||
```
|
||||
|
||||
注意上述代码中闭合函数的用法:`i`、`xi` 都是作为参数传入闭合函数的,这一做法使得每个协程(译者注:在其启动时)获得一份 `i` 和 `xi` 的单独拷贝,从而向闭合函数内部屏蔽了外层循环中的 `i` 和 `xi`变量;否则,for 循环的下一次迭代会更新所有协程中 `i` 和 `xi` 的值。另一方面,切片 `res` 没有传入闭合函数,因为协程不需要`res`的单独拷贝。切片 `res` 也在闭合函数中但并不是参数。
|
||||
注意上述代码中闭合函数的用法:`i`、`xi` 都是作为参数传入闭合函数的,这一做法使得每个协程(译者注:在其启动时)获得一份 `i` 和 `xi` 的单独拷贝,从而向闭合函数内部屏蔽了外层循环中的 `i` 和 `xi` 变量;否则,for 循环的下一次迭代会更新所有协程中 `i` 和 `xi` 的值。另一方面,切片 `res` 没有传入闭合函数,因为协程不需要 `res` 的单独拷贝。切片 `res` 也在闭合函数中但并不是参数。
|
||||
|
||||
|
||||
## 14.2.8 实现并行的 for 循环
|
||||
@@ -347,7 +347,7 @@ for i, v := range data {
|
||||
- 通道的长度(当前存放的元素个数)与当前资源被使用的数量相同
|
||||
- 容量减去通道的长度就是未处理的资源个数(标准信号量的整数值)
|
||||
|
||||
不用管通道中存放的是什么,只关注长度;因此我们创建了一个长度可变但容量为0(字节)的通道:
|
||||
不用管通道中存放的是什么,只关注长度;因此我们创建了一个长度可变但容量为 0(字节)的通道:
|
||||
|
||||
```go
|
||||
type Empty interface {}
|
||||
@@ -397,7 +397,7 @@ func (s semaphore) Signal() {
|
||||
}
|
||||
```
|
||||
|
||||
练习 14.5:[gosum.go](exercises/chapter_14/gosum.go):用这种习惯用法写一个程序,开启一个协程来计算2个整数的和并等待计算结果并打印出来。
|
||||
练习 14.5:[gosum.go](exercises/chapter_14/gosum.go):用这种习惯用法写一个程序,开启一个协程来计算 2 个整数的和并等待计算结果并打印出来。
|
||||
|
||||
练习 14.6:[producer_consumer.go](exercises/chapter_14/producer_consumer.go):用这种习惯用法写一个程序,有两个协程,第一个提供数字 0,10,20,...90 并将他们放入通道,第二个协程从通道中读取并打印。`main()` 等待两个协程完成后再结束。
|
||||
|
||||
@@ -486,7 +486,7 @@ func suck(ch chan int) {
|
||||
|
||||
习惯用法:通道迭代模式
|
||||
|
||||
这个模式用到了后边14.6章示例 [producer_consumer.go](exercises/chapter_14/producer_consumer.go) 的生产者-消费者模式,通常,需要从包含了地址索引字段 items 的容器给通道填入元素。为容器的类型定义一个方法 `Iter()`,返回一个只读的通道(参见第 [14.2.11](14.2.md#14211-通道的方向) 节)items,如下:
|
||||
这个模式用到了后边 14.6 章示例 [producer_consumer.go](exercises/chapter_14/producer_consumer.go) 的生产者-消费者模式,通常,需要从包含了地址索引字段 items 的容器给通道填入元素。为容器的类型定义一个方法 `Iter()`,返回一个只读的通道(参见第 [14.2.11](14.2.md#14211-通道的方向) 节)items,如下:
|
||||
|
||||
```go
|
||||
func (c *container) Iter () <- chan item {
|
||||
@@ -508,7 +508,7 @@ func (c *container) Iter () <- chan item {
|
||||
for x := range container.Iter() { ... }
|
||||
```
|
||||
|
||||
其运行在自己启动的协程中,所以上边的迭代用到了一个通道和两个协程(可能运行在不同的线程上)。 这样我们就有了一个典型的生产者-消费者模式。如果在程序结束之前,向通道写值的协程未完成工作,则这个协程不会被垃圾回收;这是设计使然。这种看起来并不符合预期的行为正是由通道这种线程安全的通信方式所导致的。如此一来,一个协程为了写入一个永远无人读取的通道而被挂起就成了一个bug,而并非你预想中的那样被悄悄回收掉(garbage-collected)了。
|
||||
其运行在自己启动的协程中,所以上边的迭代用到了一个通道和两个协程(可能运行在不同的线程上)。 这样我们就有了一个典型的生产者-消费者模式。如果在程序结束之前,向通道写值的协程未完成工作,则这个协程不会被垃圾回收;这是设计使然。这种看起来并不符合预期的行为正是由通道这种线程安全的通信方式所导致的。如此一来,一个协程为了写入一个永远无人读取的通道而被挂起就成了一个 bug ,而并非你预想中的那样被悄悄回收掉(garbage-collected)了。
|
||||
|
||||
习惯用法:生产者消费者模式
|
||||
|
||||
|
||||
Reference in New Issue
Block a user