回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,17 +1,17 @@
## 8.1. Goroutines
在Go言中,每一個併發的執行單元叫作一goroutine。設想這里的一程序有兩個函數,一個函數做計算,另一個輸出結果,假設兩個函數沒有相互之間的調用關繫。一個線性的程序會先調用其中的一個函數,然後再調用另一。如果程序中包含多goroutine對兩個函數的調用則可能生在同一刻。上就看到這樣的一程序。
在Go言中,每一个并发的执行单元叫作一goroutine。设想这里的一程序有两个函数,一个函数做计算,另一个输出结果,假设两个函数没有相互之间的调用关系。一个线性的程序会先调用其中的一个函数,然后再调用另一。如果程序中包含多goroutine对两个函数的调用则可能生在同一刻。上就看到这样的一程序。
如果你使用操作繫統或者其它言提供的程,那你可以簡單地把goroutine比作一個線程,這樣你就可以出一些正的程序了。goroutine和程的本質區别會在9.8節中講
如果你使用操作系统或者其它言提供的线程,那你可以简单地把goroutine比作一个线程,这样你就可以出一些正的程序了。goroutine和线程的本质区别会在9.8节中讲
當一個程序啟動時,其主函數卽在一個單獨的goroutine中行,我叫它main goroutine。新的goroutine用go語句來創建。在法上go句是一普通的函或方法調用前加上關鍵字go。go語句會使其句中的函在一個新創建的goroutine中行。而go句本身迅速地完成。
当一个程序启动时,其主函数即在一个单独的goroutine中行,我叫它main goroutine。新的goroutine用go语句来创建。在法上go句是一普通的函或方法用前加上关键字go。go语句会使其句中的函在一个新创建的goroutine中行。而go句本身迅速地完成。
```go
f() // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait
```
下面的例子main goroutine將計算菲波那契列的第45元素值。由於計算函使用低效的遞歸,所以會運行相當長時間,在此期間我們想讓用戶看到一個可見的標識來表明程序依然在正常行,所以做一個動畵的小圖標
下面的例子main goroutine将计算菲波那契列的第45元素值。由于计算函使用低效的递归,所以会运行相当长时间,在此期间我们想让用户看到一个可见的标识来表明程序依然在正常行,所以做一个动画的小图标
<u><i>gopl.io/ch8/spinner</i><u>
```go
@@ -39,12 +39,12 @@ func fib(x int) int {
}
```
動畵顯示了秒之fib(45)的調用成功地返迴,併且打印果:
动画显示了秒之fib(45)的用成功地返回,并且打印果:
```
Fibonacci(45) = 1134903170
```
主函數返迴。主函數返迴時所有的goroutine都被直接打,程序退出。除了主函退出或者直接止程序之外,有其它的程方法能夠讓一個goroutine來打斷另一個的執行,但是之可以看到一方式來實現這個目的,通goroutine之的通信來讓一個goroutine求其它的goroutine併被請求的goroutine自行結束執行。
主函数返回。主函数返回时所有的goroutine都被直接打,程序退出。除了主函退出或者直接止程序之外,有其它的程方法能够让一个goroutine来打断另一个的执行,但是之可以看到一方式来实现这个目的,通goroutine之的通信来让一个goroutine求其它的goroutine并被请求的goroutine自行结束执行。
留意一下里的兩個獨立的元是如何進行組合的spinning和菲波那契的算。分别在立的函中,但兩個函數會同時執行。
留意一下里的两个独立的元是如何进行组合的spinning和菲波那契的算。分别在立的函中,但两个函数会同时执行。

View File

@@ -1,8 +1,8 @@
## 8.2. 示例: 併發的Clock服
## 8.2. 示例: 并发的Clock服
網絡編程是併發大顯身手的一個領域,由於服務器是最典型的需要同時處理很多接的程序,這些連接一般來自遠彼此立的客端。在本小中,我們會講解go言的net包這個包提供編寫一個網絡客戶端或者服器程序的基本件,無論兩者間通信是使用TCPUDP或者Unix domain sockets。在第一章中我們已經使用的net/http包里的方法也算是net包的一部分。
网络编程是并发大显身手的一个领域,由于服务器是最典型的需要同时处理很多接的程序,这些连接一般来自远彼此立的客端。在本小中,我们会讲解go言的net包这个包提供编写一个网络客户端或者服器程序的基本件,无论两者间通信是使用TCPUDP或者Unix domain sockets。在第一章中我们已经使用的net/http包里的方法也算是net包的一部分。
的第一例子是一個順序執行的時鐘服務器,它每隔一秒鐘將當前時間寫到客端:
的第一例子是一个顺序执行的时钟服务器,它每隔一秒钟将当前时间写到客端:
<u><i>gopl.io/ch8/clock1</i></u>
```go
@@ -45,13 +45,13 @@ func handleConn(c net.Conn) {
```
Listen函數創建了一net.Listener的象,這個對象會監聽一個網絡端口上到來的連接,在這個例子里我用的是TCP的localhost:8000端口。listener象的Accept方法直接阻塞,直到一新的接被建,然後會返迴一個net.Conn對象來表示這個連接。
Listen函数创建了一net.Listener的象,这个对象会监听一个网络端口上到来的连接,在这个例子里我用的是TCP的localhost:8000端口。listener象的Accept方法直接阻塞,直到一新的接被建,然后会返回一个net.Conn对象来表示这个连接。
handleConn函數會處理一完整的客戶端連接。在一for死循中,將當前的候用time.Now()函得到,然後寫到客端。由net.Conn實現了io.Writer接口可以直接向其寫入內容。這個死循環會一直行,直到入失。最可能的原因是客端主動斷開連接。這種情況下handleConn函數會用defer調用關閉服務器側的連接,然後返迴到主函數,繼續等待下一個連接請求。
handleConn函数会处理一完整的客户端连接。在一for死循中,将当前的候用time.Now()函得到,然后写到客端。由net.Conn实现了io.Writer接口可以直接向其写入内容。这个死循环会一直行,直到入失。最可能的原因是客端主动断开连接。这种情况下handleConn函数会用defer调用关闭服务器侧的连接,然后返回到主函数,继续等待下一个连接请求。
time.Time.Format方法提供了一格式化日期和時間信息的方式。它的參數是一格式化模闆標識如何格式化時間,而這個格式化模限定Mon Jan 2 03:04:05PM 2006 UTC-0700。有8部分(週幾,月份,一月的第天,等等)。可以以任意的形式來組合前面這個模闆;出在模中的部分會作爲參考來對時間格式進行輸出。在上面的例子中我們隻用到了小、分和秒。time包里定了很多標準時間格式比如time.RFC1123。在行格式化的逆向操作time.Parse,也用到同的策略。(譯註:這是go言和其它言相比比奇葩的一地方。。你需要住格式化字符串是1月2日下午34分5秒零六年UTC-0700而不像其它言那Y-m-d H:i:s一樣,當然了里可以用1234567的方式來記憶,倒是也不麻)
time.Time.Format方法提供了一格式化日期和时间信息的方式。它的参数是一格式化模板标识如何格式化时间,而这个格式化模限定Mon Jan 2 03:04:05PM 2006 UTC-0700。有8部分(周几,月份,一月的第天,等等)。可以以任意的形式来组合前面这个模板;出在模中的部分会作为参考来对时间格式进行输出。在上面的例子中我们只用到了小、分和秒。time包里定了很多标准时间格式比如time.RFC1123。在行格式化的逆向操作time.Parse,也用到同的策略。(译注:这是go言和其它言相比比奇葩的一地方。。你需要住格式化字符串是1月2日下午34分5秒零六年UTC-0700而不像其它言那Y-m-d H:i:s一样,当然了里可以用1234567的方式来记忆,倒是也不麻)
爲了連接例子里的服器,我需要一個客戶端程序比如netcat這個工具(nc命令)這個工具可以用來執行網絡連接操作。
为了连接例子里的服器,我需要一个客户端程序比如netcat这个工具(nc命令)这个工具可以用来执行网络连接操作。
```
$ go build gopl.io/ch8/clock1
@@ -64,7 +64,7 @@ $ nc localhost 8000
^C
```
戶端將服務器發來的時間顯示了出,我用Control+C來中斷客戶端的在Unix繫統上,你看到^C這樣的響應。如果你的繫統沒有裝nc這個工具你可以用telnet來實現同樣的效果,或者也可以用我下面的這個用go寫的簡單的telnet程序用net.Dial就可以簡單地創建一TCP接:
户端将服务器发来的时间显示了出,我用Control+C来中断客户端的在Unix系统上,你看到^C这样的响应。如果你的系统没有装nc这个工具你可以用telnet来实现同样的效果,或者也可以用我下面的这个用go写的简单的telnet程序用net.Dial就可以简单地创建一TCP接:
<u><i>gopl.io/ch8/netcat1</i></u>
```go
@@ -94,7 +94,7 @@ func mustCopy(dst io.Writer, src io.Reader) {
}
```
這個程序會從連接中讀取數據,併將讀到的內容寫到標準輸出中直到遇到end of file的件或者發生錯誤。mustCopy這個函數我們在本節的幾個例子中都用到。讓我們同時運行兩個客戶端來進行一個測試,這里可以開兩個終端窗口,下面左的是其中的一個的輸出,右的是另一個的輸出:
这个程序会从连接中读取数据,并将读到的内容写到标准输出中直到遇到end of file的件或者发生错误。mustCopy这个函数我们在本节的几个例子中都用到。让我们同时运行两个客户端来进行一个测试,这里可以开两个终端窗口,下面左的是其中的一个的输出,右的是另一个的输出:
```
$ go build gopl.io/ch8/netcat1
@@ -110,9 +110,9 @@ $ ./netcat1
$ killall clock1
```
killall命令是一Unix命令行工具可以用定的程名來殺掉所有名字匹配的程。
killall命令是一Unix命令行工具可以用定的程名来杀掉所有名字匹配的程。
第二個客戶端必等待第一個客戶端完成工作,這樣服務端才能繼續向後執行;因爲我們這里的服器程序同一時間隻能處理一個客戶端連接。我們這里對服務端程序做一小改,使其支持併發在handleConn函數調用的地方增加go關鍵字,每一次handleConn的調用都入一個獨立的goroutine。
第二个客户端必等待第一个客户端完成工作,这样服务端才能继续向后执行;因为我们这里的服器程序同一时间只能处理一个客户端连接。我们这里对服务端程序做一小改,使其支持并发在handleConn函数调用的地方增加go关键字,每一次handleConn的用都入一个独立的goroutine。
<u><i>gopl.io/ch8/clock2</i></u>
```go
@@ -127,7 +127,7 @@ for {
```
在多個客戶端可以同接收到時間了:
在多个客户端可以同接收到时间了:
```
$ go build gopl.io/ch8/clock2
@@ -147,7 +147,7 @@ $ ./netcat1
$ killall clock2
```
**練習 8.1** 改clock2支持傳入參數作爲端口,然後寫一個clockwall的程序這個程序可以同時與多個clock服器通信,多服器中讀取時間,併且在一表格中一次示所有服務傳迴的結果,類似於你在某些公室里看到的時鐘牆。如果你有地理上分布式的服器可以用的話,讓這些服器跑在不同的器上面;或者在同一台器上跑多不同的例,這些實例監聽不同的端口,假自己在不同的時區。像下面這樣
**练习 8.1** 改clock2支持传入参数作为端口,然后写一个clockwall的程序这个程序可以同时与多个clock服器通信,多服器中读取时间,并且在一表格中一次示所有服务传回的结果,类似于你在某些公室里看到的时钟墙。如果你有地理上分布式的服器可以用的话,让这些服器跑在不同的器上面;或者在同一台器上跑多不同的例,这些实例监听不同的端口,假自己在不同的时区。像下面这样
```
$ TZ=US/Eastern ./clock2 -port 8010 &
@@ -156,4 +156,4 @@ $ TZ=Europe/London ./clock2 -port 8030 &
$ clockwall NewYork=localhost:8010 Tokyo=localhost:8020 London=localhost:8030
```
**練習 8.2** 實現一個併發FTP服器。服務器應該解析客戶端來的一些命令比如cd命令來切換目録ls列出目録內文件get和send來傳輸文件close來關閉連接。你可以用標準的ftp命令來作爲客戶端,或者也可以自己實現一個
**练习 8.2** 实现一个并发FTP服器。服务器应该解析客户端来的一些命令比如cd命令来切换目录ls列出目录内文件get和send来传输文件close来关闭连接。你可以用标准的ftp命令来作为客户端,或者也可以自己实现一个

View File

@@ -1,6 +1,6 @@
## 8.3. 示例: 併發的Echo服
## 8.3. 示例: 并发的Echo服
clock服器每一個連接都起一goroutine。在本中我們會創建一echo服器,這個服務在每個連接中有多goroutine。大多echo服務僅僅會返迴他們讀取到的容,就像下面這個簡單的handleConn函所做的一
clock服器每一个连接都起一goroutine。在本中我们会创建一echo服器,这个服务在每个连接中有多goroutine。大多echo服务仅仅会返回他们读取到的容,就像下面这个简单的handleConn函所做的一
```go
func handleConn(c net.Conn) {
@@ -9,7 +9,7 @@ func handleConn(c net.Conn) {
}
```
更有意思的echo服務應該模擬一個實際的echo的“迴響”,且一始要用大HELLO表示“音很大”,之後經過一小段延遲返迴一個有所和的Hello後一個全小字母的hello表示聲音漸漸變小直至消失,像下面這個版本的handleConn(譯註:笑看作者洞大)
更有意思的echo服务应该模拟一个实际的echo的“回响”,且一始要用大HELLO表示“音很大”,之后经过一小段延迟返回一个有所和的Hello后一个全小字母的hello表示声音渐渐变小直至消失,像下面这个版本的handleConn(译注:笑看作者洞大)
<u><i>gopl.io/ch8/reverb1</i></u>
```go
@@ -31,7 +31,7 @@ func handleConn(c net.Conn) {
}
```
需要陞級我們的客端程序,這樣它就可以發送終端的入到服器,把服端的返迴輸出到端上,使我有了使用併發的另一個好機會
需要升级我们的客端程序,这样它就可以发送终端的入到服器,把服端的返回输出到端上,使我有了使用并发的另一个好机会
<u><i>gopl.io/ch8/netcat2</i></u>
```go
@@ -46,10 +46,10 @@ func main() {
}
```
main goroutine從標準輸入流中讀取內容併將其發送給服務器時,另一goroutine會讀取併打印服端的響應。當main goroutine碰到輸入終止時,例如,用戶在終端中按了Control-D(^D)在windows上是Control-Z這時程序就會被終止,管其它goroutine中還有進行中的任。(在8.4.1中引入了channels後我們會明白如何程序等待兩邊都結束)。
main goroutine从标准输入流中读取内容并将其发送给服务器时,另一goroutine会读取并打印服端的响应。当main goroutine碰到输入终止时,例如,用户在终端中按了Control-D(^D)在windows上是Control-Z这时程序就会被终止,管其它goroutine中还有进行中的任。(在8.4.1中引入了channels后我们会明白如何程序等待两边都结束)。
下面這個會話中,客端的入是左對齊的,服端的響應會用縮進來區别顯示。
戶端會向服器“喊三次”:
下面这个会话中,客端的入是左对齐的,服端的响应会用缩进来区别显示。
户端会向服器“喊三次”:
```
$ go build gopl.io/ch8/reverb1
@@ -72,7 +72,7 @@ yooo-hooo!
$ killall reverb1
```
意客端的第三次shout在前一shout理完成之前一直有被理,貌似看起不是特别“現實”。眞實世界里的迴響應該是會由三次shout的迴聲組合而成的。了模擬眞實世界的迴響,我需要更多的goroutine來做這件事情。這樣我們就再一次地需要go這個關鍵詞了,次我用它來調用echo
意客端的第三次shout在前一shout理完成之前一直有被理,貌似看起不是特别“现实”。真实世界里的回响应该是会由三次shout的回声组合而成的。了模拟真实世界的回响,我需要更多的goroutine来做这件事情。这样我们就再一次地需要go这个关键词了,次我用它来调用echo
<u><i>gopl.io/ch8/reverb2</i></u>
```go
@@ -86,8 +86,8 @@ func handleConn(c net.Conn) {
}
```
go跟的函數的參數會在go句自身執行時被求值因此input.Text()在main goroutine中被求值。
現在迴響是併發併且會按時間來覆蓋掉其它響應了:
go跟的函数的参数会在go句自身执行时被求值因此input.Text()在main goroutine中被求值。
现在回响是并发并且会按时间来覆盖掉其它响应了:
```
$ go build gopl.io/ch8/reverb2
@@ -105,4 +105,4 @@ Yooo-hooo!
$ killall reverb2
```
讓服務使用併發不隻是處理多個客戶端的求,甚至在處理單個連接時也可能用到,就像我上面的兩個go關鍵詞的用法。然而在我使用go關鍵詞的同,需要慎重地考net.Conn中的方法在併發地調用時是否安全,事實上對於大多數類型來説也確實不安全。我們會在下一章中詳細地探討併發安全性。
让服务使用并发不只是处理多个客户端的求,甚至在处理单个连接时也可能用到,就像我上面的两个go关键词的用法。然而在我使用go关键词的同,需要慎重地考net.Conn中的方法在并发地调用时是否安全,事实上对于大多数类型来说也确实不安全。我们会在下一章中详细地探讨并发安全性。

View File

@@ -1,14 +1,14 @@
### 8.4.1. 不帶緩存的Channels
### 8.4.1. 不带缓存的Channels
個基於無緩存Channels的送操作將導致發送者goroutine阻塞直到另一goroutine在相同的Channels上行接收操作,當發送的值通Channels成功傳輸之後,兩個goroutine可以繼續執行後面的句。反之,如果接收操作先生,那接收者goroutine也阻塞,直到有另一goroutine在相同的Channels上執行發送操作。
个基于无缓存Channels的送操作将导致发送者goroutine阻塞直到另一goroutine在相同的Channels上行接收操作,当发送的值通Channels成功传输之后,两个goroutine可以继续执行后面的句。反之,如果接收操作先生,那接收者goroutine也阻塞,直到有另一goroutine在相同的Channels上执行发送操作。
於無緩存Channels的送和接收操作將導致兩個goroutine做一次同步操作。因爲這個原因,無緩存Channels有候也被稱爲同步Channels。當通過一個無緩存Channels發送數據時,接收者收到數據發生在喚醒發送者goroutine之前譯註*happens before*是Go語言併發內存模型的一個關鍵術語!)。
于无缓存Channels的送和接收操作将导致两个goroutine做一次同步操作。因为这个原因,无缓存Channels有候也被称为同步Channels。当通过一个无缓存Channels发送数据时,接收者收到数据发生在唤醒发送者goroutine之前译注*happens before*是Go语言并发内存模型的一个关键术语!)。
討論併發編程時,當我們説x事件在y事件之前生(*happens before*),我們併不是x事件在時間上比y時間更早;我要表的意思是要保在此之前的事件都已完成了,例如在此之前的更新某些量的操作已完成,你可以放心依賴這些已完成的事件了。
讨论并发编程时,当我们说x事件在y事件之前生(*happens before*),我们并不是x事件在时间上比y时间更早;我要表的意思是要保在此之前的事件都已完成了,例如在此之前的更新某些量的操作已完成,你可以放心依赖这些已完成的事件了。
當我們説x事件不是在y事件之前生也不是在y事件之後發生,我們就説x事件和y事件是併發的。這併不是意味着x事件和y事件就一定是同時發生的,我們隻是不能確定這兩個事件生的先後順序。在下一章中我們將看到,當兩個goroutine併發訪問了相同的變量時,我有必要保某些事件的執行順序,以避免出某些併發問題
当我们说x事件不是在y事件之前生也不是在y事件之后发生,我们就说x事件和y事件是并发的。这并不是意味着x事件和y事件就一定是同时发生的,我们只是不能确定这两个事件生的先后顺序。在下一章中我们将看到,当两个goroutine并发访问了相同的变量时,我有必要保某些事件的执行顺序,以避免出某些并发问题
在8.3的客端程序它在主goroutine中譯註:就是行main函的goroutine將標準輸入複製到server因此當客戶端程序關閉標準輸入時,後台goroutine可能依然在工作。我需要主goroutine等待台goroutine完成工作再退出,我使用了一channel同步兩個goroutine
在8.3的客端程序它在主goroutine中译注:就是行main函的goroutine将标准输入复制到server因此当客户端程序关闭标准输入时,后台goroutine可能依然在工作。我需要主goroutine等待台goroutine完成工作再退出,我使用了一channel同步两个goroutine
<u><i>gopl.io/ch8/netcat3</i></u>
```Go
@@ -29,11 +29,11 @@ func main() {
}
```
當用戶關閉了標準輸主goroutine中的mustCopy函數調用將返迴,然後調用conn.Close()關閉讀和寫方向的網絡連接。關閉網絡鏈接中的方向的鏈接將導致server程序收到一文件end-of-file束的信號。關閉網絡鏈接中方向的鏈接將導致後台goroutine的io.Copy函數調用返迴一個“read from closed connection”從關閉的鏈接讀”)似的錯誤,因此我們臨時移除了錯誤日誌語句;在練習8.3將會提供一更好的解方案。(需要意的是go語句調用了一個函數字面量,Go言中啟動goroutine常用的形式。
当用户关闭了标准输主goroutine中的mustCopy函数调用将返回,然后调用conn.Close()关闭读和写方向的网络连接。关闭网络链接中的方向的链接将导致server程序收到一文件end-of-file束的信号。关闭网络链接中方向的链接将导致后台goroutine的io.Copy函数调用返回一个“read from closed connection”从关闭的链接读”)似的错误,因此我们临时移除了错误日志语句;在练习8.3将会提供一更好的解方案。(需要意的是go语句调用了一个函数字面量,Go言中启动goroutine常用的形式。
台goroutine返之前,它先打印一個日誌信息,然向done對應的channel送一值。主goroutine在退出前先等待done對應的channel接收一值。因此,是可以在程序退出前正確輸出“done”消息。
台goroutine返之前,它先打印一个日志信息,然向done对应的channel送一值。主goroutine在退出前先等待done对应的channel接收一值。因此,是可以在程序退出前正确输出“done”消息。
channels送消息有兩個重要方面。首先每消息都有一值,但是有候通的事實和發生的刻也同重要。當我們更希望強調通訊發生的時刻時,我們將它稱爲**消息事件**。有些消息事件併不攜帶額外的信息,它僅僅是用作兩個goroutine之的同步,這時候我可以用`struct{}`結構體作爲channels元素的型,然也可以使用bool或int類型實現同樣的功能,`done <- 1`句也比`done <- struct{}{}`更短。
channels送消息有两个重要方面。首先每消息都有一值,但是有候通的事实和发生的刻也同重要。当我们更希望强调通讯发生的时刻时,我们将它称为**消息事件**。有些消息事件并不携带额外的信息,它仅仅是用作两个goroutine之的同步,这时候我可以用`struct{}`结构体作为channels元素的型,然也可以使用bool或int类型实现同样的功能,`done <- 1`句也比`done <- struct{}{}`更短。
**練習 8.3** 在netcat3例子中conn然是一interface型的值,但是其底層眞實類型是`*net.TCPConn`,代表一TCP接。一TCP接有讀和寫兩個部分可以使用CloseRead和CloseWrite方法分别關閉它們。脩改netcat3的主goroutine代碼,隻關閉網絡鏈接中的部分,這樣的話後台goroutine可以在標準輸入被關閉後繼續打印reverb1服務器傳迴的數據要在reverb2服器也完成同的功能是比較睏難的;考**練習 8.4**。)
**练习 8.3** 在netcat3例子中conn然是一interface型的值,但是其底层真实类型是`*net.TCPConn`,代表一TCP接。一TCP接有读和写两个部分可以使用CloseRead和CloseWrite方法分别关闭它们。修改netcat3的主goroutine代码,只关闭网络链接中的部分,这样的话后台goroutine可以在标准输入被关闭后继续打印reverb1服务器传回的数据要在reverb2服器也完成同的功能是比较困难的;考**练习 8.4**。)

View File

@@ -1,10 +1,10 @@
### 8.4.2. 串的ChannelsPipeline
### 8.4.2. 串的ChannelsPipeline
Channels也可以用於將多個goroutine接在一起,一Channels的出作下一Channels的入。這種串聯的Channels就是所的管道pipeline。下面的程序用兩個channels將三個goroutine串聯起來,如8.1所示。
Channels也可以用于将多个goroutine接在一起,一Channels的出作下一Channels的入。这种串联的Channels就是所的管道pipeline。下面的程序用两个channels将三个goroutine串联起来,如8.1所示。
![](../images/ch8-01.png)
第一goroutine是一個計數器,用生成0、1、2、……形式的整序列,然後通過channel將該整數序列發送給第二goroutine第二goroutine是一求平方的程序,收到的每個整數求平方,然後將平方後的結果通第二channel發送給第三goroutine第三goroutine是一打印程序,打印收到的每個整數。爲了保持例子清晰,我有意選擇了非常簡單的函數,當然三goroutine的算很簡單,在現實中確實沒有必要如此簡單的運算構建三goroutine。
第一goroutine是一个计数器,用生成0、1、2、……形式的整序列,然后通过channel将该整数序列发送给第二goroutine第二goroutine是一求平方的程序,收到的每个整数求平方,然后将平方后的结果通第二channel发送给第三goroutine第三goroutine是一打印程序,打印收到的每个整数。为了保持例子清晰,我有意选择了非常简单的函数,当然三goroutine的算很简单,在现实中确实没有必要如此简单的运算构建三goroutine。
<u><i>gopl.io/ch8/pipeline1</i></u>
```Go
@@ -34,17 +34,17 @@ func main() {
}
```
如您所料,上面的程序生成0、1、4、9、……形式的無窮數列。像這樣的串Channels的管道Pipelines可以用在需要長時間運行的服中,每個長時間運行的goroutine可能包含一死循在不同goroutine的死循環內部使用串的Channels通信。但是,如果我希望通Channels隻發送有限的數列該如何理呢?
如您所料,上面的程序生成0、1、4、9、……形式的无穷数列。像这样的串Channels的管道Pipelines可以用在需要长时间运行的服中,每个长时间运行的goroutine可能包含一死循在不同goroutine的死循环内部使用串的Channels通信。但是,如果我希望通Channels只发送有限的数列该如何理呢?
如果送者知道,有更多的值需要送到channel的,那麽讓接收者也能及知道有多的值可接收是有用的,因接收者可以停止不必要的接收等待。可以通過內置的close函數來關閉channel實現
如果送者知道,有更多的值需要送到channel的,那么让接收者也能及知道有多的值可接收是有用的,因接收者可以停止不必要的接收等待。可以通过内置的close函数来关闭channel实现
```Go
close(naturals)
```
當一個channel被關閉後,再向channel發送數據將導致panic常。當一個被關閉的channel中已經發送的數據都被成功接收後,後續的接收操作不再阻塞,它們會立卽返迴一個零值。關閉上面例子中的naturals變量對應的channel不能止循,它依然收到一個永無休止的零值序列,然後將它們發送給打印者goroutine。
当一个channel被关闭后,再向channel发送数据将导致panic常。当一个被关闭的channel中已经发送的数据都被成功接收后,后续的接收操作不再阻塞,它们会立即返回一个零值。关闭上面例子中的naturals变量对应的channel不能止循,它依然收到一个永无休止的零值序列,然后将它们发送给打印者goroutine。
沒有辦法直接測試一個channel是否被關閉,但是接收操作有一個變體形式:它多接收一個結果,多接收的第二個結果是一個布爾值okture表示成功channels接收到值false表示channels已經被關閉併且里面有值可接收。使用這個特性,我可以改squarer函中的循環代碼,當naturals對應的channel被關閉併沒有值可接收跳出循環,併且也關閉squares對應的channel.
没有办法直接测试一个channel是否被关闭,但是接收操作有一个变体形式:它多接收一个结果,多接收的第二个结果是一个布尔值okture表示成功channels接收到值false表示channels已经被关闭并且里面有值可接收。使用这个特性,我可以改squarer函中的循环代码,当naturals对应的channel被关闭并没有值可接收跳出循环,并且也关闭squares对应的channel.
```Go
// Squarer
@@ -60,9 +60,9 @@ go func() {
}()
```
上面的法是笨拙的,而且這種處理模式很因此Go言的range循可直接在channels上面迭代。使用range循是上面理模式的簡潔語法,它依次channel接收數據,當channel被關閉併且沒有值可接收跳出循
上面的法是笨拙的,而且这种处理模式很因此Go言的range循可直接在channels上面迭代。使用range循是上面理模式的简洁语法,它依次channel接收数据,当channel被关闭并且没有值可接收跳出循
在下面的改中,我們的計數器goroutine生成100個含數字的序列,然後關閉naturals對應的channel這將導致計算平方的squarer對應的goroutine可以正常止循環併關閉squares對應的channel。在一個更複雜的程序中,可以通defer語句關閉對應的channel。主goroutine也可以正常止循環併退出程序。
在下面的改中,我们的计数器goroutine生成100个含数字的序列,然后关闭naturals对应的channel这将导致计算平方的squarer对应的goroutine可以正常止循环并关闭squares对应的channel。在一个更复杂的程序中,可以通defer语句关闭对应的channel。主goroutine也可以正常止循环并退出程序。
<u><i>gopl.io/ch8/pipeline2</i></u>
```Go
@@ -93,8 +93,8 @@ func main() {
}
```
實你併不需要關閉每一channel。隻要當需要告接收者goroutine所有的數據已經全部發送時才需要關閉channel。不管一channel是否被關閉,當它沒有被引用時將會被Go言的垃圾自動迴收器收。(不要將關閉一個打開文件的操作和關閉一個channel操作混淆。對於每個打開的文件,都需要在不使用的使用調用對應的Close方法來關閉文件。)
实你并不需要关闭每一channel。只要当需要告接收者goroutine所有的数据已经全部发送时才需要关闭channel。不管一channel是否被关闭,当它没有被引用时将会被Go言的垃圾自动回收器收。(不要将关闭一个打开文件的操作和关闭一个channel操作混淆。对于每个打开的文件,都需要在不使用的使用调用对应的Close方法来关闭文件。)
視圖重複關閉一個channel將導致panic常,視圖關閉一個nil值的channel也將導致panic常。關閉一個channels還會觸發一個廣播機製,我們將在8.9節討論
视图重复关闭一个channel将导致panic常,视图关闭一个nil值的channel也将导致panic常。关闭一个channels还会触发一个广播机制,我们将在8.9节讨论

View File

@@ -1,6 +1,6 @@
### 8.4.3. 方向的Channel
### 8.4.3. 方向的Channel
着程序的增,人們習慣於將大的函拆分小的函。我前面的例子中使用了三goroutine後用兩個channels連鏈接它,它都是main函的局部量。將三個goroutine拆分以下三個函數是自然的想法:
着程序的增,人们习惯于将大的函拆分小的函。我前面的例子中使用了三goroutine后用两个channels连链接它,它都是main函的局部量。将三个goroutine拆分以下三个函数是自然的想法:
```Go
func counter(out chan int)
@@ -8,15 +8,15 @@ func squarer(out, in chan int)
func printer(in chan int)
```
其中squarer算平方的函數在兩個串聯Channels的中,因此擁有兩個channels型的參數,一個用於輸入一個用於輸出。每channels都用有相同的型,但是它的使用方式想反:一個隻用於接收,另一個隻用於發送。參數的名字in和out已經明確表示了這個意圖,但是併無法保squarer函向一in參數對應的channels發送數據或者從一個out參數對應的channels接收數據
其中squarer算平方的函数在两个串联Channels的中,因此拥有两个channels型的参数,一个用于输入一个用于输出。每channels都用有相同的型,但是它的使用方式想反:一个只用于接收,另一个只用于发送。参数的名字in和out已经明确表示了这个意图,但是并无法保squarer函向一in参数对应的channels发送数据或者从一个out参数对应的channels接收数据
這種場景是典型的。當一個channel作爲一個函數參數是,它一般是被專門用於隻發送或者接收。
这种场景是典型的。当一个channel作为一个函数参数是,它一般是被专门用于只发送或者接收。
了表明這種意圖併防止被Go言的類型繫統提供了方向的channel型,分别用於隻發送或接收的channel。`chan<- int`表示一個隻發送int的channel隻能發送不能接收。相反,`<-chan int`表示一個隻接收int的channel能接收不能送。(箭`<-`關鍵字chan的相位置表明了channel的方向。這種限製將在編譯期檢測
了表明这种意图并防止被Go言的类型系统提供了方向的channel型,分别用于只发送或接收的channel。`chan<- int`表示一个只发送int的channel只能发送不能接收。相反,`<-chan int`表示一个只接收int的channel能接收不能送。(箭`<-`关键字chan的相位置表明了channel的方向。这种限制将在编译期检测
爲關閉操作隻用於斷言不再向channel送新的數據,所以有在送者所在的goroutine才會調用close函,因此對一個隻接收的channel調用close是一個編譯錯誤
为关闭操作只用于断言不再向channel送新的数据,所以有在送者所在的goroutine才会调用close函,因此对一个只接收的channel用close是一个编译错误
是改的版本,一次參數使用了方向channel型:
是改的版本,一次参数使用了方向channel型:
<u><i>gopl.io/ch8/pipeline3</i></u>
```Go
@@ -49,6 +49,6 @@ func main() {
}
```
調用counter(naturals)將導致將`chan int`型的naturals式地轉換爲`chan<- int`類型隻發送型的channel。調用printer(squares)也會導致相似的隱式轉換,這一次是轉換爲`<-chan int`類型隻接收型的channel。任何向channel向向channel量的值操作都將導致該隱式轉換。這里併沒有反向轉換的語法:也就是不能一個將類`chan<- int`型的向型的channel轉換爲`chan int`型的向型的channel。
用counter(naturals)将导致将`chan int`型的naturals式地转换为`chan<- int`类型只发送型的channel。用printer(squares)也会导致相似的隐式转换,这一次是转换为`<-chan int`类型只接收型的channel。任何向channel向向channel量的值操作都将导致该隐式转换。这里并没有反向转换的语法:也就是不能一个将类`chan<- int`型的向型的channel转换为`chan int`型的向型的channel。

View File

@@ -1,6 +1,6 @@
### 8.4.4. 帶緩存的Channels
### 8.4.4. 带缓存的Channels
帶緩存的Channel部持有一元素列。列的最大容量是在調用make函數創建channel時通過第二個參數指定的。下面的語句創建了一可以持有三字符串元素的帶緩存Channel。8.2是ch變量對應的channel的形表示形式。
带缓存的Channel部持有一元素列。列的最大容量是在用make函数创建channel时通过第二个参数指定的。下面的语句创建了一可以持有三字符串元素的带缓存Channel。8.2是ch变量对应的channel的形表示形式。
```Go
ch = make(chan string, 3)
@@ -8,9 +8,9 @@ ch = make(chan string, 3)
![](../images/ch8-02.png)
存Channel的送操作就是向內部緩存隊列的尾部插入元素,接收操作則是從隊列的頭部刪除元素。如果內部緩存隊列是滿的,那麽發送操作阻塞直到因另一goroutine行接收操作而放了新的列空。相反如果channel是空的接收操作阻塞直到有另一goroutine執行發送操作而向列插入元素。
存Channel的送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是的,那么发送操作阻塞直到因另一goroutine行接收操作而放了新的列空。相反如果channel是空的接收操作阻塞直到有另一goroutine执行发送操作而向列插入元素。
可以在阻塞的情況下連續向新建的channel送三值:
可以在阻塞的情况下连续向新建的channel送三值:
```Go
ch <- "A"
@@ -18,42 +18,42 @@ ch <- "B"
ch <- "C"
```
此刻channel的內部緩存隊列將是滿的(8.3),如果有第四個發送操作將發生阻塞。
此刻channel的内部缓存队列将是满的(8.3),如果有第四个发送操作将发生阻塞。
![](../images/ch8-03.png)
如果我接收一值,
如果我接收一值,
```Go
fmt.Println(<-ch) // "A"
```
channel的緩存隊列將不是滿的也不是空的(8.4),因此對該channel行的送或接收操作都不會發送阻塞。通過這種方式channel的緩存隊列解耦了接收和送的goroutine。
channel的缓存队列将不是的也不是空的(8.4),因此对该channel行的送或接收操作都不会发送阻塞。通过这种方式channel的缓存队列解耦了接收和送的goroutine。
![](../images/ch8-04.png)
在某些特殊情程序可能需要知道channel內部緩存的容量,可以用置的cap函數獲取:
在某些特殊情程序可能需要知道channel内部缓存的容量,可以用置的cap函数获取:
```Go
fmt.Println(cap(ch)) // "3"
```
樣,對於內置的len函,如果入的是channel麽將返迴channel內部緩存隊列中有效元素的個數。因爲在併發程序中信息會隨着接收操作而失效,但是它某些故障診斷和性能優化會有幫助。
样,对于内置的len函,如果入的是channel么将返回channel内部缓存队列中有效元素的个数。因为在并发程序中信息会随着接收操作而失效,但是它某些故障诊断和性能优化会有帮助。
```Go
fmt.Println(len(ch)) // "2"
```
繼續執行兩次接收操作channel部的緩存隊列將又成空的,如果有第四接收操作將發生阻塞:
继续执行两次接收操作channel部的缓存队列将又成空的,如果有第四接收操作将发生阻塞:
```Go
fmt.Println(<-ch) // "B"
fmt.Println(<-ch) // "C"
```
這個例子中,送和接收操作都生在同一goroutine中但是在是的程序中它一般由不同的goroutine行。Go言新手有時候會將一個帶緩存的channel作同一goroutine中的列使用,雖然語法看似簡單,但實際上這是一個錯誤。Channel和goroutine的調度器機製是緊密相的,一個發送操作——或是整程序——可能會永遠阻塞。如果你是需要一個簡單的隊使用slice就可以了。
这个例子中,送和接收操作都生在同一goroutine中但是在是的程序中它一般由不同的goroutine行。Go言新手有时候会将一个带缓存的channel作同一goroutine中的列使用,虽然语法看似简单,但实际上这是一个错误。Channel和goroutine的度器机制是紧密相的,一个发送操作——或是整程序——可能会永远阻塞。如果你是需要一个简单的队使用slice就可以了。
下面的例子展示了一使用了帶緩存channel的用。它併發地向三個鏡像站點發出請求,三個鏡像站分散在不同的地理位置。它分别收到的響應發送到帶緩存channel接收者接收第一收到的響應,也就是最快的那個響應。因此mirroredQuery函可能在另外兩個響應慢的像站點響應之前就返迴了結果。(順便説一下,多goroutines併發地向同一channel發送數據,或同一channel接收數據都是常的用法。)
下面的例子展示了一使用了带缓存channel的用。它并发地向三个镜像站点发出请求,三个镜像站分散在不同的地理位置。它分别收到的响应发送到带缓存channel接收者接收第一收到的响应,也就是最快的那个响应。因此mirroredQuery函可能在另外两个响应慢的像站点响应之前就返回了结果。(顺便说一下,多goroutines并发地向同一channel发送数据,或同一channel接收数据都是常的用法。)
```Go
func mirroredQuery() string {
@@ -67,18 +67,18 @@ func mirroredQuery() string {
func request(hostname string) (response string) { /* ... */ }
```
如果我使用了無緩存的channel麽兩個慢的goroutines將會因爲沒有人接收而被永卡住。這種情況,稱爲goroutines漏,這將是一BUG。和垃圾量不同,漏的goroutines併不會被自動迴收,因此保每不再需要的goroutine能正常退出是重要的。
如果我使用了无缓存的channel么两个慢的goroutines将会因为没有人接收而被永卡住。这种情况,称为goroutines漏,这将是一BUG。和垃圾量不同,漏的goroutines并不会被自动回收,因此保每不再需要的goroutine能正常退出是重要的。
關於無緩存或帶緩存channels之間的選擇,或者是帶緩存channels的容量大小的選擇,都可能影程序的正性。無緩存channel更地保了每個發送操作與相應的同步接收操作;但是對於帶緩存channel些操作是解耦的。同樣,卽使我知道將要發送到一channel的信息的量上限,建一個對應容量大小帶緩存channel也是不現實的,因爲這要求在行任何接收操作之前存所有已經發送的值。如果未能分配足夠的緩衝將導致程序死
关于无缓存或带缓存channels之间的选择,或者是带缓存channels的容量大小的选择,都可能影程序的正性。无缓存channel更地保了每个发送操作与相应的同步接收操作;但是对于带缓存channel些操作是解耦的。同样,即使我知道将要发送到一channel的信息的量上限,建一个对应容量大小带缓存channel也是不现实的,因为这要求在行任何接收操作之前存所有已经发送的值。如果未能分配足够的缓冲将导致程序死
Channel的存也可能影程序的性能。想象一家蛋糕店有三個廚師,一烘焙,一上糖衣,有一個將每個蛋糕傳遞到它下一個廚師在生産線。在小的房空間環境,每個廚師在完成蛋糕後必須等待下一個廚師已經準備好接受它;這類似於在一個無緩存的channel上進行溝通。
Channel的存也可能影程序的性能。想象一家蛋糕店有三个厨师,一烘焙,一上糖衣,有一个将每个蛋糕传递到它下一个厨师在生产线。在小的房空间环境,每个厨师在完成蛋糕后必须等待下一个厨师已经准备好接受它;这类似于在一个无缓存的channel上进行沟通。
如果在每個廚師之間有一放置一蛋糕的外空,那麽每個廚師就可以將一個完成的蛋糕臨時放在那里而馬上進入下一蛋糕在作中;這類似於將channel的緩存隊列的容量設置爲1。要每個廚師的平均工作效率相近,那其中大部分的傳輸工作是迅速的,個體之間細小的效率差異將在交接程中瀰補。如果廚師之間有更大的外空——也是就更大容量的緩存隊列——可以在不停止生産線的前提下消除更大的效率波,例如一個廚師可以短地休息,然在加快趕上進度而不影其其他人。
如果在每个厨师之间有一放置一蛋糕的外空,那么每个厨师就可以将一个完成的蛋糕临时放在那里而马上进入下一蛋糕在作中;这类似于将channel的缓存队列的容量设置为1。要每个厨师的平均工作效率相近,那其中大部分的传输工作是迅速的,个体之间细小的效率差异将在交接程中弥补。如果厨师之间有更大的外空——也是就更大容量的缓存队列——可以在不停止生产线的前提下消除更大的效率波,例如一个厨师可以短地休息,然在加快赶上进度而不影其其他人。
另一方面,如果生産線的前期段一直快於後續階段,那麽它們之間的緩存在大部分時間都將是滿的。相反,如果後續階段比前期段更快,那麽它們之間的緩存在大部分時間都將是空的。對於這類場景,外的緩存併沒有帶來任何好
另一方面,如果生产线的前期段一直快于后续阶段,那么它们之间的缓存在大部分时间都将是满的。相反,如果后续阶段比前期段更快,那么它们之间的缓存在大部分时间都将是空的。对于这类场景,外的缓存并没有带来任何好
産線的隱喻對於理解channels和goroutines的工作機製是很有助的。例如,如果第二段是需要精心作的複雜操作,一個廚師可能法跟上第一個廚師的進度,或者是無法滿足第階段廚師的需求。要解決這個問題,我可以雇另一個廚師來幫助完成第二段的工作,他行相同的任但是立工作。這類似於基於相同的channels建另一個獨立的goroutine。
产线的隐喻对于理解channels和goroutines的工作机制是很有助的。例如,如果第二段是需要精心作的复杂操作,一个厨师可能法跟上第一个厨师的进度,或者是无法满足第阶段厨师的需求。要解决这个问题,我可以雇另一个厨师来帮助完成第二段的工作,他行相同的任但是立工作。这类似于基于相同的channels建另一个独立的goroutine。
們沒有太多的空展示全部細節但是gopl.io/ch8/cake包模擬了這個蛋糕店,可以通不同的參數調整。它還對上面提到的幾種場景提供對應的基準測試§11.4
们没有太多的空展示全部细节但是gopl.io/ch8/cake包模拟了这个蛋糕店,可以通不同的参数调整。它还对上面提到的几种场景提供对应的基准测试§11.4

View File

@@ -1,18 +1,18 @@
## 8.4. Channels
如果goroutine是Go音程序的併發體的話,那channels它們之間的通信機製。一channels是一通信機製,它可以讓一個goroutine通過它給另一goroutine送值信息。每channel都有一特殊的也就是channels可發送數據的類型。一可以送int類型數據的channel一般寫爲chan int。
如果goroutine是Go音程序的并发体的话,那channels它们之间的通信机制。一channels是一通信机制,它可以让一个goroutine通过它给另一goroutine送值信息。每channel都有一特殊的也就是channels可发送数据的类型。一可以送int类型数据的channel一般写为chan int。
使用置的make函,我可以建一channel
使用置的make函,我可以建一channel
```Go
ch := make(chan int) // ch has type 'chan int'
```
和mapchannel也一個對應make建的底層數據結構的引用。當我們複製一個channel或用於函數參數傳遞時我們隻是拷了一channel引用因此調用者何被調用者引用同一channel象。和其它的引用型一channel的零值也是nil。
和mapchannel也一个对应make建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时我们只是拷了一channel引用因此用者何被用者引用同一channel象。和其它的引用型一channel的零值也是nil。
兩個相同型的channel可以使用==算符比。如果兩個channel引用的是相通的象,那麽比較的結果爲眞。一channel也可以和nil行比
两个相同型的channel可以使用==算符比。如果两个channel引用的是相通的象,那么比较的结果为真。一channel也可以和nil行比
channel有送和接受兩個主要操作,都是通信行。一個發送語句將一個值從一個goroutine通channel送到另一個執行接收操作的goroutine。送和接收兩個操作都是用`<-`算符。在發送語句中,`<-`算符分割channel和要送的值。在接收句中,`<-`算符在channel象之前。一不使用接收果的接收操作也是合法的。
channel有送和接受两个主要操作,都是通信行。一个发送语句将一个值从一个goroutine通channel送到另一个执行接收操作的goroutine。送和接收两个操作都是用`<-`算符。在发送语句中,`<-`算符分割channel和要送的值。在接收句中,`<-`算符在channel象之前。一不使用接收果的接收操作也是合法的。
```Go
ch <- x // a send statement
@@ -20,15 +20,15 @@ x = <-ch // a receive expression in an assignment statement
<-ch // a receive statement; result is discarded
```
Channel支持close操作於關閉channel隨後對基於該channel的任何送操作都將導致panic常。對一個已經被close的channel之行接收操作依然可以接受到之前已成功送的數據如果channel中已經沒有數據的話講産生一零值的數據
Channel支持close操作于关闭channel随后对基于该channel的任何送操作都将导致panic常。对一个已经被close的channel之行接收操作依然可以接受到之前已成功送的数据如果channel中已经没有数据的话讲产生一零值的数据
使用置的close函就可以關閉一個channel
使用置的close函就可以关闭一个channel
```Go
close(ch)
```
以最簡單方式調用make函數創建的時一個無緩存的channel但是我也可以指定第二整形參數,對應channel的容量。如果channel的容量大零,那麽該channel就是帶緩存的channel。
以最简单方式用make函数创建的时一个无缓存的channel但是我也可以指定第二整形参数,对应channel的容量。如果channel的容量大零,那么该channel就是带缓存的channel。
```Go
ch = make(chan int) // unbuffered channel
@@ -36,7 +36,7 @@ ch = make(chan int, 0) // unbuffered channel
ch = make(chan int, 3) // buffered channel with capacity 3
```
們將先討論無緩存的channel在8.4.4節討論帶緩存的channel。
们将先讨论无缓存的channel在8.4.4节讨论带缓存的channel。
{% include "./ch8-04-1.md" %}

View File

@@ -1,6 +1,6 @@
## 8.5. 併發的循
## 8.5. 并发的循
中,我們會探索一些用來在併行時循環迭代的常見併發模型。我們會探究全尺寸片生成一些縮略圖的問題。gopl.io/ch8/thumbnail包提供了ImageFile函數來幫我們拉伸片。我們不會説明這個函數的實現,隻需要gopl.io下它。
中,我们会探索一些用来在并行时循环迭代的常见并发模型。我们会探究全尺寸片生成一些缩略图的问题。gopl.io/ch8/thumbnail包提供了ImageFile函数来帮我们拉伸片。我们不会说明这个函数的实现,只需要gopl.io下它。
<u><i>gopl.io/ch8/thumbnail</i></u>
```go
@@ -12,7 +12,7 @@ package thumbnail
func ImageFile(infile string) (string, error)
```
下面的程序會循環迭代一些片文件名,併爲每一張圖片生成一個縮略圖
下面的程序会循环迭代一些片文件名,并为每一张图片生成一个缩略图
<u><i>gopl.io/ch8/thumbnail</i></u>
```go
@@ -26,9 +26,9 @@ func makeThumbnails(filenames []string) {
}
```
然我們處理文件的順序無關緊要,因每一個圖片的拉伸操作和其它片的理操作都是彼此立的。像這種子問題都是完全彼此立的問題被叫做易併行問題(譯註embarrassingly parallel譯的話更像是尷尬併行)。易併行問題是最容易被實現成併行的一類問題(廢話)且是最能享受併發帶來的好,能夠隨着併行的規模線性地展。
然我们处理文件的顺序无关紧要,因每一个图片的拉伸操作和其它片的理操作都是彼此立的。像这种子问题都是完全彼此立的问题被叫做易并行问题(译注embarrassingly parallel译的话更像是尴尬并行)。易并行问题是最容易被实现成并行的一类问题(废话)且是最能享受并发带来的好,能够随着并行的规模线性地展。
下面讓我們併行地執行這些操作,從而將文件IO的延遲隱藏掉,用上多核cpu的算能力拉伸像。我的第一個併發程序是使用了一go關鍵字。里我先忽略掉錯誤,之後再進行處理。
下面让我们并行地执行这些操作,从而将文件IO的延迟隐藏掉,用上多核cpu的算能力拉伸像。我的第一个并发程序是使用了一go关键字。里我先忽略掉错误,之后再进行处理。
```go
// NOTE: incorrect!
@@ -39,9 +39,9 @@ func makeThumbnails2(filenames []string) {
}
```
這個版本行的在有太快,實際上,由它比最早的版本使用的時間要短得多,卽使當文件名的slice中包含有一元素。就有奇怪了,如果程序沒有併發執行的,那爲什麽一個併發的版本是要快呢?答案其是makeThumbnails在它還沒有完成工作之前就已經返迴了。它啟動了所有的goroutine沒一個文件名對應一個,但有等待它一直到行完
这个版本行的在有太快,实际上,由它比最早的版本使用的时间要短得多,即使当文件名的slice中包含有一元素。就有奇怪了,如果程序没有并发执行的,那为什么一个并发的版本是要快呢?答案其是makeThumbnails在它还没有完成工作之前就已经返回了。它启动了所有的goroutine没一个文件名对应一个,但有等待它一直到行完
有什直接的法能等待goroutine完成但是我可以改goroutine里的代碼讓其能夠將完成情況報告給外部的goroutine知,使用的方式是向一共享的channel中送事件。因爲我們已經知道部的goroutine有len(filenames)所以外部的goroutine需要在返之前對這些事件計數
有什直接的法能等待goroutine完成但是我可以改goroutine里的代码让其能够将完成情况报告给外部的goroutine知,使用的方式是向一共享的channel中送事件。因为我们已经知道部的goroutine有len(filenames)所以外部的goroutine需要在返之前对这些事件计数
```go
// makeThumbnails3 makes thumbnails of the specified files in parallel.
@@ -60,7 +60,7 @@ func makeThumbnails3(filenames []string) {
}
```
意我們將f的值作爲一個顯式的變量傳給了函,而不是在循環的閉包中明:
意我们将f的值作为一个显式的变量传给了函,而不是在循环的闭包中明:
```go
for _, f := range filenames {
@@ -71,9 +71,9 @@ for _, f := range filenames {
}
```
迴憶一下之前在5.6.1中,匿名函中的循環變量快照問題。上面這個單獨的變量f是被所有的匿名函值所共享,且會被連續的循迭代所更新的。新的goroutine開始執行字面函數時for循可能已更新了f併且開始了另一的迭代或者(更有可能的)已經結束了整個循環,所以當這些goroutine開始讀取f的值,它所看到的值已是slice的最後一個元素了。式地添加這個參數,我們能夠確保使用的f是go語句執行時的“前”那f。
回忆一下之前在5.6.1中,匿名函中的循环变量快照问题。上面这个单独的变量f是被所有的匿名函值所共享,且会被连续的循迭代所更新的。新的goroutine开始执行字面函数时for循可能已更新了f并且开始了另一的迭代或者(更有可能的)已经结束了整个循环,所以当这些goroutine开始读取f的值,它所看到的值已是slice的最后一个元素了。式地添加这个参数,我们能够确保使用的f是go语句执行时的“前”那f。
如果我想要每一worker goroutine往主goroutine中返迴值時該怎麽辦呢?當我們調用thumbnail.ImageFile建文件失敗的時候,它會返迴一個錯誤。下一版本的makeThumbnails會返迴其在做拉伸操作接收到的第一個錯誤
如果我想要每一worker goroutine往主goroutine中返回值时该怎么办呢?当我们调用thumbnail.ImageFile建文件失败的时候,它会返回一个错误。下一版本的makeThumbnails会返回其在做拉伸操作接收到的第一个错误
```go
// makeThumbnails4 makes thumbnails for the specified files in parallel.
@@ -98,11 +98,11 @@ func makeThumbnails4(filenames []string) error {
}
```
這個程序有一微秒的bug。它遇到第一非nil的error時會直接error返迴到調用方,使得有一goroutine去排空errors channel。這樣剩下的worker goroutine在向這個channel中送值,都會永遠地阻塞下去,且永都不退出。這種情況叫做goroutine露(§8.4.4),可能會導致整程序卡住或者跑出out of memory的錯誤
这个程序有一微秒的bug。它遇到第一非nil的error时会直接error返回到调用方,使得有一goroutine去排空errors channel。这样剩下的worker goroutine在向这个channel中送值,都会永远地阻塞下去,且永都不退出。这种情况叫做goroutine露(§8.4.4),可能会导致整程序卡住或者跑出out of memory的错误
簡單的解決辦法就是用一具有合大小的buffered channel這樣這些worker goroutine向channel中發送測向時就不被阻塞。(一個可選的解決辦法是建一另外的goroutinemain goroutine返第一個錯誤的同去排空channel)
简单的解决办法就是用一具有合大小的buffered channel这样这些worker goroutine向channel中发送测向时就不被阻塞。(一个可选的解决办法是建一另外的goroutinemain goroutine返第一个错误的同去排空channel)
下一版本的makeThumbnails使用了一buffered channel來返迴生成的片文件的名字,附生成時的錯誤
下一版本的makeThumbnails使用了一buffered channel来返回生成的片文件的名字,附生成时的错误
```go
// makeThumbnails5 makes thumbnails for the specified files in parallel.
@@ -135,9 +135,9 @@ func makeThumbnails5(filenames []string) (thumbfiles []string, err error) {
}
```
們最後一個版本的makeThumbnails返了新文件的大小總計數(bytes)。和前面的版本都不一的一是我們在這個版本里有把文件名放在slice里而是通過一個string的channel傳過來,所以我們無法對循環的次數進行預測
们最后一个版本的makeThumbnails返了新文件的大小总计数(bytes)。和前面的版本都不一的一是我们在这个版本里有把文件名放在slice里而是通过一个string的channel传过来,所以我们无法对循环的次数进行预测
了知道最後一個goroutine什麽時候結束(最後一個結束併不一定是最後一個開始),我需要一個遞增的計數器,在每一goroutine啟動時加一在goroutine退出時減一。需要一特殊的計數器,這個計數器需要在多goroutine操作做到安全且提供提供在其減爲零之前一直等待的一方法。這種計數類型被稱爲sync.WaitGroup下面的代就用到了這種方法:
了知道最后一个goroutine什么时候结束(最后一个结束并不一定是最后一个开始),我需要一个递增的计数器,在每一goroutine启动时加一在goroutine退出时减一。需要一特殊的计数器,这个计数器需要在多goroutine操作做到安全且提供提供在其减为零之前一直等待的一方法。这种计数类型被称为sync.WaitGroup下面的代就用到了这种方法:
```go
// makeThumbnails6 makes thumbnails for each file received from the channel.
@@ -174,14 +174,14 @@ func makeThumbnails6(filenames <-chan string) int64 {
}
```
意Add和Done方法的不策。Add是爲計數器加一,必在worker goroutine始之前調而不是在goroutine中則的話我們沒辦法確定Add是在"closer" goroutine調用Wait之前被調用。且Add有一個參數但Done卻沒有任何參數;其它和Add(-1)是等的。我使用defer來確保計數器卽使是在出的情下依然能夠正確地被掉。上面的程序代碼結構是當我們使用併發循環,但又不知道迭代次數時很通常而且很地道的法。
意Add和Done方法的不策。Add是为计数器加一,必在worker goroutine始之前而不是在goroutine中则的话我们没办法确定Add是在"closer" goroutine用Wait之前被用。且Add有一个参数但Done却没有任何参数;其它和Add(-1)是等的。我使用defer来确保计数器即使是在出的情下依然能够正确地被掉。上面的程序代码结构是当我们使用并发循环,但又不知道迭代次数时很通常而且很地道的法。
sizes channel攜帶了每一文件的大小到main goroutine在main goroutine中使用了range loop來計算總和。察一下我是怎樣創建一closer goroutine併讓其等待worker們在關閉掉sizes channel之前退出的。步操作wait和close是基sizes的循環的併發。考一下另一方案如果等待操作被放在了main goroutine中在循之前,這樣的話就永都不會結束了,如果在循環之後,那麽又變成了不可的部分,因爲沒有任何西去關閉這個channel這個循環就永都不會終止。
sizes channel携带了每一文件的大小到main goroutine在main goroutine中使用了range loop来计算总和。察一下我是怎样创建一closer goroutine并让其等待worker们在关闭掉sizes channel之前退出的。步操作wait和close是基sizes的循环的并发。考一下另一方案如果等待操作被放在了main goroutine中在循之前,这样的话就永都不会结束了,如果在循环之后,那么又变成了不可的部分,因为没有任何西去关闭这个channel这个循环就永都不会终止。
8.5 表明了makethumbnails6函中事件的序列。列表示goroutine。窄段代表sleep段代表活。斜線箭頭代表用同步兩個goroutine的事件。時間向下流動。註意main goroutine是如何大部分的時間被喚醒執行其range循等待worker送值或者closer來關閉channel的。
8.5 表明了makethumbnails6函中事件的序列。列表示goroutine。窄线段代表sleep线段代表活。斜线箭头代表用同步两个goroutine的事件。时间向下流动。注意main goroutine是如何大部分的时间被唤醒执行其range循等待worker送值或者closer来关闭channel的。
![](../images/ch8-05.png)
**練習 8.4** 改reverb2服器,在每一個連接中使用sync.WaitGroup來計數活躍的echo goroutine。當計數減爲零時,關閉TCP接的入,像練習8.3中一樣。驗證一下你的改版netcat3客戶端會一直等待所有的併發“喊叫”完成,使是在標準輸入流已經關閉的情下。
**练习 8.4** 改reverb2服器,在每一个连接中使用sync.WaitGroup来计数活跃的echo goroutine。当计数减为零时,关闭TCP接的入,像练习8.3中一样。验证一下你的改版netcat3客户端会一直等待所有的并发“喊叫”完成,使是在标准输入流已经关闭的情下。
**練習 8.5** 使用一已有的CPU定的序程序比如在3.3中我們寫的Mandelbrot程序或者3.2中的3-D surface算程序,併將他們的主循環改爲併發形式使用channel來進行通信。在多核計算機上這個程序得到了多少速度上的改?使用多少goroutine是最合的呢?
**练习 8.5** 使用一已有的CPU定的序程序比如在3.3中我们写的Mandelbrot程序或者3.2中的3-D surface算程序,并将他们的主循环改为并发形式使用channel来进行通信。在多核计算机上这个程序得到了多少速度上的改?使用多少goroutine是最合的呢?

View File

@@ -1,6 +1,6 @@
## 8.6. 示例: 併發的Web爬
## 8.6. 示例: 并发的Web爬
在5.6中,我做了一個簡單的web爬用bfs(廣度優先)算法抓取整個網站。在本中,我們會讓這個這個爬蟲併行化,這樣每一彼此立的抓取命令可以併行進行IO最大化利用網絡資源。crawl函和gopl.io/ch5/findlinks3中的是一的。
在5.6中,我做了一个简单的web爬用bfs(广度优先)算法抓取整个网站。在本中,我们会让这个这个爬虫并行化,这样每一彼此立的抓取命令可以并行进行IO最大化利用网络资源。crawl函和gopl.io/ch5/findlinks3中的是一的。
<u><i>gopl.io/ch8/crawl1</i></u>
```go
@@ -14,7 +14,7 @@ func crawl(url string) []string {
}
```
主函和5.6中的breadthFirst(深度先)似。像之前一,一worklist是一個記録了需要理的元素的列,每一元素都是一需要抓取的URL列表過這一次我用channel代替slice來做這個隊列。每一個對crawl的調用都在他自己的goroutine中進行併且會把他抓到的鏈接發送迴worklist。
主函和5.6中的breadthFirst(深度先)似。像之前一,一worklist是一个记录了需要理的元素的列,每一元素都是一需要抓取的URL列表过这一次我用channel代替slice来做这个队列。每一个对crawl的用都在他自己的goroutine中进行并且会把他抓到的链接发送回worklist。
```go
func main() {
@@ -38,9 +38,9 @@ func main() {
}
```
註意這里的crawl所在的goroutine會將link作爲一個顯式的參數傳入,避免“循環變量快照”的問題(在5.6.1中有解)。另外註意這里將命令行參數傳入worklist也是在一另外的goroutine中行的,這是爲了避免在main goroutine和crawler goroutine中同向另一goroutine通channel發送內容時發生死(因另一的接收操作還沒有準備好)。然,里我也可以用buffered channel來解決問題,這里不再述。
注意这里的crawl所在的goroutine会将link作为一个显式的参数传入,避免“循环变量快照”的问题(在5.6.1中有解)。另外注意这里将命令行参数传入worklist也是在一另外的goroutine中行的,这是为了避免在main goroutine和crawler goroutine中同向另一goroutine通channel发送内容时发生死(因另一的接收操作还没有准备好)。然,里我也可以用buffered channel来解决问题,这里不再述。
在爬可以高併發地運行起來,併且可以生一大坨的URL了過還是會有倆問題。一個問題是在行一段時間後可能會出現在log的錯誤信息里的:
在爬可以高并发地运行起来,并且可以生一大坨的URL了过还是会有俩问题。一个问题是在行一段时间后可能会出现在log的错误信息里的:
```
@@ -56,13 +56,13 @@ https://golang.org/blog/
...
```
最初的錯誤信息是一個讓人莫名的DNS找失敗,卽使這個域名是完全可靠的。而隨後的錯誤信息揭示了原因:這個程序一次性建了太多網絡連接,超了每一個進程的打文件數限製,旣而導致了在調用net.Dial像DNS找失敗這樣的問題
最初的错误信息是一个让人莫名的DNS找失败,即使这个域名是完全可靠的。而随后的错误信息揭示了原因:这个程序一次性建了太多网络连接,超了每一个进程的打文件数限制,既而导致了在用net.Dial像DNS找失败这样的问题
這個程序在是太他媽併行了。無窮無盡地併行化不是什好事情,因不管怎麽説,你的繫統總是會有一些限因素比如CPU核心數會限製你的計算負載,比如你的硬盤轉軸和磁頭數限製了你的本地磁IO操作率,比如你的網絡帶寬限製了你的下速度上限,或者是你的一web服的服容量上限等等。了解決這個問題,我可以限製併發程序所使用的資源來使之適應自己的運行環境。對於我們的例子來説,最簡單的方法就是限製對links.Extract在同一時間最多不有超n次調用,里的n是fd的limit-20一般情下。這個一個夜店里限客人目是一道理,隻有當有客人離開時,才會允許新的客人入店內(譯註:作者你老流氓)。
这个程序在是太他妈并行了。无穷无尽地并行化不是什好事情,因不管怎么说,你的系统总是会有一些限因素比如CPU核心数会限制你的计算负载,比如你的硬盘转轴和磁头数限制了你的本地磁IO操作率,比如你的网络带宽限制了你的下速度上限,或者是你的一web服的服容量上限等等。了解决这个问题,我可以限制并发程序所使用的资源来使之适应自己的运行环境。对于我们的例子来说,最简单的方法就是限制对links.Extract在同一时间最多不有超n次用,里的n是fd的limit-20一般情下。这个一个夜店里限客人目是一道理,只有当有客人离开时,才会允许新的客人入店内(译注:作者你老流氓)。
可以用一有容量限的buffered channel來控製併發,這類似於操作繫統里的計數信號量概念。概念上channel里的n空槽代表n可以處理內容的token(通行)channel里接收一個值會釋放其中的一token且生成一新的空槽位。這樣保證了在有接收介入最多有n個發送操作。(里可能我拿channel里填充的槽做token更直一些,不過還是這樣吧~)。由channel里的元素類型併不重要,我用一零值的struct{}來作爲其元素。
可以用一有容量限的buffered channel来控制并发,这类似于操作系统里的计数信号量概念。概念上channel里的n空槽代表n可以处理内容的token(通行)channel里接收一个值会释放其中的一token且生成一新的空槽位。这样保证了在有接收介入最多有n个发送操作。(里可能我拿channel里填充的槽做token更直一些,不过还是这样吧~)。由channel里的元素类型并不重要,我用一零值的struct{}来作为其元素。
讓我們重寫crawl函數,將對links.Extract的調用操作用取、放token的操作包裹起來,來確保同一時間對其隻有20個調用。信號量數量和其能操作的IO資源數量應保持接近。
让我们重写crawl函数,将对links.Extract的用操作用取、放token的操作包裹起来,来确保同一时间对其只有20个调用。信号量数量和其能操作的IO资源数量应保持接近。
<u><i>gopl.io/ch8/crawl2</i></u>
```go
@@ -82,7 +82,7 @@ func crawl(url string) []string {
}
```
第二個問題是這個程序永都不會終止,使它已爬到了所有初始接衍生出的接。(然,除非你慎重地選擇了合的初始化URL或者已經實現了練習8.6中的深度限,你應該還沒有意識到這個問題)。了使這個程序能夠終止,我需要在worklist空或者有crawl的goroutine在運行時退出主循
第二个问题是这个程序永都不会终止,使它已爬到了所有初始接衍生出的接。(然,除非你慎重地选择了合的初始化URL或者已经实现了练习8.6中的深度限,你应该还没有意识到这个问题)。了使这个程序能够终止,我需要在worklist空或者有crawl的goroutine在运行时退出主循
```go
func main() {
@@ -111,11 +111,11 @@ func main() {
}
```
這個版本中,算器nworklist的送操作數量進行了限。每一次我們發現有元素需要被送到worklist,我們都會對n進行++操作在向worklist中送初始的命令行參數之前,我們也進行過一次++操作。里的操作++是在每啟動一個crawler的goroutine之前。主循環會在n減爲0時終止這時候説明沒活可了。
这个版本中,算器nworklist的送操作数量进行了限。每一次我们发现有元素需要被送到worklist,我们都会对n进行++操作在向worklist中送初始的命令行参数之前,我们也进行过一次++操作。里的操作++是在每启动一个crawler的goroutine之前。主循环会在n减为0时终止这时候说明没活可了。
現在這個併發爬蟲會比5.6中的深度優先蒐索版快上20倍而且不出什麽錯,併且在其完成任務時也會正確地終止。
现在这个并发爬虫会比5.6中的深度优先搜索版快上20倍而且不出什么错,并且在其完成任务时也会正确地终止。
下面的程序是避免過度併發的另一思路。這個版本使用了原的crawl函,但有使用計數信號取而代之用了20個長活的crawler goroutine這樣來保證最多20HTTP求在併發
下面的程序是避免过度并发的另一思路。这个版本使用了原的crawl函,但有使用计数信号取而代之用了20个长活的crawler goroutine这样来保证最多20HTTP求在并发
```go
func main() {
@@ -149,16 +149,16 @@ func main() {
}
```
所有的爬goroutine在都是被同一channel-unseenLinks餵飽的了。主goroutine負責拆分它worklist里拿到的元素後把沒有抓過的經由unseenLinks channel發送給一個爬蟲的goroutine。
所有的爬goroutine在都是被同一channel-unseenLinks喂饱的了。主goroutine负责拆分它worklist里拿到的元素后把没有抓过的经由unseenLinks channel发送给一个爬虫的goroutine。
seen這個map被限定在main goroutine中也就是説這個map能在main goroutine中進行訪問。類似於其它的信息藏方式,這樣的約束可以讓我們從一定程度上保程序的正性。例如,內部變量不能在函外部被訪問到;量(§2.3.4)在有被轉義的情下是法在函外部訪問的;一個對象的封字段法被該對象的方法以外的方法訪問到。在所有的情下,信息藏都可以助我們約束我的程序,使其不生意料之外的情
seen这个map被限定在main goroutine中也就是说这个map能在main goroutine中进行访问。类似于其它的信息藏方式,这样的约束可以让我们从一定程度上保程序的正性。例如,内部变量不能在函外部被访问到;量(§2.3.4)在有被转义的情下是法在函外部访问的;一个对象的封字段法被该对象的方法以外的方法访问到。在所有的情下,信息藏都可以助我们约束我的程序,使其不生意料之外的情
crawl函爬到的接在一個專有的goroutine中被送到worklist中避免死鎖。爲了節省空間,這個例子的終止問題我們先不進行詳細闡述了。
crawl函爬到的接在一个专有的goroutine中被送到worklist中避免死锁。为了节省空间,这个例子的终止问题我们先不进行详细阐述了。
**練習 8.6** 爲併發爬蟲增加深度限。也就是,如果用戶設置了depth=3麽隻有從首頁跳轉三次以內能夠跳到的面才能被抓取到。
**练习 8.6** 为并发爬虫增加深度限。也就是,如果用户设置了depth=3么只有从首页跳转三次以内能够跳到的面才能被抓取到。
**練習 8.7** 完成一個併發程序來創建一個線上網站的本地像,把該站點的所有可達的頁面都抓取到本地硬盤。爲了省事,我們這里可以取出現在該域下的所有面(比如golang.org尾,譯註:外鏈的應該就不算了。)然了,出現在頁面里的接你也需要行一些理,使其能在你的像站點上進行跳,而不是指向原始的接。
**练习 8.7** 完成一个并发程序来创建一个线上网站的本地像,把该站点的所有可达的页面都抓取到本地硬盘。为了省事,我们这里可以取出现在该域下的所有面(比如golang.org尾,译注:外链的应该就不算了。)然了,出现在页面里的接你也需要行一些理,使其能在你的像站点上进行跳,而不是指向原始的接。
**譯註**
拓展閲讀 [Handling 1 Million Requests per Minute with Go](http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/)。
**译注**
拓展阅读 [Handling 1 Million Requests per Minute with Go](http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/)。

View File

@@ -1,6 +1,6 @@
## 8.7. 基select的多路
## 8.7. 基select的多路
下面的程序會進行火箭射的倒計時。time.Tick函數返迴一個channel程序會週期性地像一個節拍器一樣向這個channel送事件。每一事件的值是一個時間戳,不更有意思的是其送方式。
下面的程序会进行火箭射的倒计时。time.Tick函数返回一个channel程序会周期性地像一个节拍器一样向这个channel送事件。每一事件的值是一个时间戳,不更有意思的是其送方式。
<u><i>gopl.io/ch8/countdown1</i></u>
```go
@@ -15,7 +15,7 @@ func main() {
}
```
在我們讓這個程序支持在倒計時中,用按下return鍵時直接中斷發射流程。首先,我們啟動一個goroutine這個goroutine會嚐試從標準輸入中調入一個單獨的byte且,如果成功了,向名abort的channel送一值。
在我们让这个程序支持在倒计时中,用按下return键时直接中断发射流程。首先,我们启动一个goroutine这个goroutine会尝试从标准输入中入一个单独的byte且,如果成功了,向名abort的channel送一值。
<u><i>gopl.io/ch8/countdown2</i></u>
```go
@@ -26,7 +26,7 @@ go func() {
}()
```
在每一次計數循環的迭代都需要等待兩個channel中的其中一個返迴事件了ticker channel一切正常(就像NASA jorgon的"nominal"譯註:這梗估計我們是不懂了)或者異常時返迴的abort事件。我們無法做到每一channel中接收信息如果我們這麽做的,如果第一channel中有事件發過來那麽程序就立刻被阻塞,這樣我們就無法收到第二channel中發過來的事件。這時候我需要多路用(multiplex)些操作了,了能多路用,我使用了select句。
在每一次计数循环的迭代都需要等待两个channel中的其中一个返回事件了ticker channel一切正常(就像NASA jorgon的"nominal"译注:这梗估计我们是不懂了)或者异常时返回的abort事件。我们无法做到每一channel中接收信息如果我们这么做的,如果第一channel中有事件发过来那么程序就立刻被阻塞,这样我们就无法收到第二channel中发过来的事件。这时候我需要多路用(multiplex)些操作了,了能多路用,我使用了select句。
```go
select {
@@ -41,11 +41,11 @@ default:
}
```
上面是select句的一般形式。和switch句稍微有相似,也會有幾個case和最的default選擇支。每一case代表一通信操作(在某channel上進行發送或者接收)併且會包含一些語句組成的一個語句塊。一接收表式可能包含接收表式自身(譯註:不把接收到的值賦值給變量什的),就像上面的第一case或者包含在一個簡短的變量聲明中,像第二case里一;第二形式你能引用接收到的值。
上面是select句的一般形式。和switch句稍微有相似,也会有几个case和最的default选择支。每一case代表一通信操作(在某channel上进行发送或者接收)并且会包含一些语句组成的一个语句块。一接收表式可能包含接收表式自身(译注:不把接收到的值赋值给变量什的),就像上面的第一case或者包含在一个简短的变量声明中,像第二case里一;第二形式你能引用接收到的值。
select等待case中有能夠執行的case時去執行。當條件滿足時select才去通信併執行case之後的語句;這時候其它通信是不會執行的。一個沒有任何case的select語句寫作select{}會永遠地等待下去。
select等待case中有能够执行的case时去执行。当条件满足时select才去通信并执行case之后的语句;这时候其它通信是不会执行的。一个没有任何case的select语句写作select{}会永远地等待下去。
讓我們迴到我的火箭射程序。time.After函數會立卽返迴一個channel起一新的goroutine在經過特定的時間後向該channel送一個獨立的值。下面的select語句會會一直等待到兩個事件中的一個到達,無論是abort事件或者一10秒經過的事件。如果10秒經過了還沒有abort事件入,那火箭就會發射。
让我们回到我的火箭射程序。time.After函数会立即返回一个channel起一新的goroutine在经过特定的时间后向该channel送一个独立的值。下面的select语句会会一直等待到两个事件中的一个到达,无论是abort事件或者一10秒经过的事件。如果10秒经过了还没有abort事件入,那火箭就会发射。
```go
func main() {
@@ -64,7 +64,7 @@ func main() {
```
下面這個例子更微秒。ch這個channel的buffer大小是1所以交替的空或爲滿,所以有一case可以行下去,無論i是奇或者偶,它都打印0 2 4 6 8。
下面这个例子更微秒。ch这个channel的buffer大小是1所以交替的空或为满,所以有一case可以行下去,无论i是奇或者偶,它都打印0 2 4 6 8。
```go
ch := make(chan int, 1)
@@ -77,9 +77,9 @@ for i := 0; i < 10; i++ {
}
```
如果多case同時就緒時select會隨機地選擇一個執行,這樣來保證每一channel都有平等的被select的機會。增加前一例子的buffer大小使其輸出變得不定,因爲當buffer旣不爲滿也不爲空時select句的行情就像是拋硬幣的行爲一樣是隨機的。
如果多case同时就绪时select会随机地选择一个执行,这样来保证每一channel都有平等的被select的机会。增加前一例子的buffer大小使其输出变得不定,因为当buffer既不为满也不为空时select句的行情就像是抛硬币的行为一样是随机的。
下面讓我們的發射程序打印倒計時。這里的select語句會使每次循迭代等待一秒來執行退出操作。
下面让我们的发射程序打印倒计时。这里的select语句会使每次循迭代等待一秒来执行退出操作。
<u><i>gopl.io/ch8/countdown3</i></u>
```go
@@ -102,9 +102,9 @@ func main() {
}
```
time.Tick函數表現得好像它建了一在循環中調用time.Sleep的goroutine每次被喚醒時發送一事件。countdown函數返迴時,它停止tick中接收事件但是ticker這個goroutine依然存活,繼續徒勞地嚐試從channel中送值,然而這時候已經沒有其它的goroutine會從該channel中接收值了--這被稱爲goroutine露(§8.4.4)。
time.Tick函数表现得好像它建了一在循环中调用time.Sleep的goroutine每次被唤醒时发送一事件。countdown函数返回时,它停止tick中接收事件但是ticker这个goroutine依然存活,继续徒劳地尝试从channel中送值,然而这时候已经没有其它的goroutine会从该channel中接收值了--这被称为goroutine露(§8.4.4)。
Tick函挺方便,但是隻有當程序整生命期都需要這個時間時我們使用它才比較合適。否則的話,我們應該使用下面的這種模式:
Tick函挺方便,但是只有当程序整生命期都需要这个时间时我们使用它才比较合适。否则的话,我们应该使用下面的这种模式:
```go
ticker := time.NewTicker(1 * time.Second)
@@ -112,9 +112,9 @@ ticker := time.NewTicker(1 * time.Second)
ticker.Stop() // cause the ticker's goroutine to terminate
```
候我希望能夠從channel中送或者接收值,避免因爲發送或者接收致的阻塞,尤其是channel沒有準備好寫或者讀時。select句就可以實現這樣的功能。select有一default來設置當其它的操作都不能夠馬上被處理時程序需要行哪些邏輯
候我希望能够从channel中送或者接收值,避免因为发送或者接收致的阻塞,尤其是channel没有准备好写或者读时。select句就可以实现这样的功能。select有一default来设置当其它的操作都不能够马上被处理时程序需要行哪些逻辑
下面的select語句會在abort channel中有值時,從其中接收值;無值時什麽都不做。是一非阻塞的接收操作;反地做這樣的操作叫做“輪詢channel”。
下面的select语句会在abort channel中有值时,从其中接收值;无值时什么都不做。是一非阻塞的接收操作;反地做这样的操作叫做“轮询channel”。
```go
select {
@@ -126,8 +126,8 @@ default:
}
```
channel的零值是nil。也許會讓你覺得比奇怪nil的channel有候也是有一些用的。因爲對一個nil的channel送和接收操作會永遠阻塞在select句中操作nil的channel永都不被select到。
channel的零值是nil。也许会让你觉得比奇怪nil的channel有候也是有一些用的。因为对一个nil的channel送和接收操作会永远阻塞在select句中操作nil的channel永都不被select到。
使得我可以用nil激活或者禁用case來達成處理其它入或出事件時超時和取消的邏輯。我們會在下一中看到一例子。
使得我可以用nil激活或者禁用case来达成处理其它入或出事件时超时和取消的逻辑。我们会在下一中看到一例子。
**練習 8.8** 使用select改造8.3中的echo服器,其增加超時,這樣服務器可以在客端10秒中有任何喊話時自動斷開連接。
**练习 8.8** 使用select改造8.3中的echo服器,其增加超时,这样服务器可以在客端10秒中有任何喊话时自动断开连接。

View File

@@ -1,6 +1,6 @@
## 8.8. 示例: 併發的字典遍
## 8.8. 示例: 并发的字典遍
在本小中,我們會創建一程序生成指定目的硬使用情況報告,這個程序和Unix里的du工具比相似。大多工作用下面這個walkDir函數來完成,這個函數使用dirents函數來枚舉一個目録下的所有入口。
在本小中,我们会创建一程序生成指定目的硬使用情况报告,这个程序和Unix里的du工具比相似。大多工作用下面这个walkDir函数来完成,这个函数使用dirents函数来枚举一个目录下的所有入口。
<u><i>gopl.io/ch8/du1</i></u>
```go
@@ -28,9 +28,9 @@ func dirents(dir string) []os.FileInfo {
}
```
ioutil.ReadDir函數會返迴一個os.FileInfo型的sliceos.FileInfo型也是os.Stat這個函數的返值。每一子目而言walkDir會遞歸地調用其自身,併且會對每一文件也遞歸調用。walkDir函數會向fileSizes這個channel送一消息。這條消息包含了文件的字大小。
ioutil.ReadDir函数会返回一个os.FileInfo型的sliceos.FileInfo型也是os.Stat这个函数的返值。每一子目而言walkDir会递归地调用其自身,并且会对每一文件也递归调用。walkDir函数会向fileSizes这个channel送一消息。这条消息包含了文件的字大小。
下面的主函,用了兩個goroutine。台的goroutine調用walkDir來遍歷命令行出的每一個路徑併最終關閉fileSizes這個channel。主goroutine會對其從channel中接收到的文件大小進行纍加,併輸出其和。
下面的主函,用了两个goroutine。台的goroutine用walkDir来遍历命令行出的每一个路径并最终关闭fileSizes这个channel。主goroutine会对其从channel中接收到的文件大小进行累加,并输出其和。
```go
package main
@@ -74,7 +74,7 @@ func printDiskUsage(nfiles, nbytes int64) {
}
```
這個程序在打印其果之前卡住很長時間
这个程序在打印其果之前卡住很长时间
```
$ go build gopl.io/ch8/du1
@@ -82,9 +82,9 @@ $ ./du1 $HOME /usr /bin /etc
213201 files 62.7 GB
```
如果在行的候能夠讓我們知道處理進度的想必更好。但是,如果簡單地把printDiskUsage函數調用移到循環里會導致其打印出成百上韆的輸出。
如果在行的候能够让我们知道处理进度的想必更好。但是,如果简单地把printDiskUsage函数调用移到循环里会导致其打印出成百上千的输出。
下面這個du的變種會間歇打印容,不過隻有在調用時提供了-v的flag才會顯示程序度信息。在roots目上循環的後台goroutine在里保持不。主goroutine在使用了計時器來每500ms生成事件用select語句來等待文件大小的消息更新大小數據,或者一個計時器的事件打印前的大小數據。如果-v的flag在運行時沒有傳入的tick這個channel保持nil這樣在select里的case也就相當於被禁用了。
下面这个du的变种会间歇打印容,不过只有在调用时提供了-v的flag才会显示程序度信息。在roots目上循环的后台goroutine在里保持不。主goroutine在使用了计时器来每500ms生成事件用select语句来等待文件大小的消息更新大小数据,或者一个计时器的事件打印前的大小数据。如果-v的flag在运行时没有传入的tick这个channel保持nil这样在select里的case也就相当于被禁用了。
<u><i>gopl.io/ch8/du2</i></u>
```go
@@ -116,9 +116,9 @@ loop:
}
```
於我們的程序不再使用range循,第一select的case必須顯式地判fileSizes的channel是不是已經被關閉了,里可以用到channel接收的二值形式。如果channel已經被關閉了的,程序直接退出循環。這里的break句用到了標籤break這樣可以同時終結select和for兩個循環;如果有用標籤就break的話隻會退出內層的select循,而外的for循環會使之入下一select循
于我们的程序不再使用range循,第一select的case必须显式地判fileSizes的channel是不是已经被关闭了,里可以用到channel接收的二值形式。如果channel已经被关闭了的,程序直接退出循环。这里的break句用到了标签break这样可以同时终结select和for两个循环;如果有用标签就break的话只会退出内层的select循,而外的for循环会使之入下一select循
在程序會悠閒地爲我們打印更新流:
在程序会悠闲地为我们打印更新流:
```
$ go build gopl.io/ch8/du2
@@ -131,7 +131,7 @@ $ ./du2 -v $HOME /usr /bin /etc
213201 files 62.7 GB
```
然而這個程序還是會花上很長時間才會結束。無法對walkDir做行化處理沒什麽别的原因,非是因爲磁盤繫統併行限。下面這個第三版本的du會對每一walkDir的調用創建一新的goroutine。它使用sync.WaitGroup (§8.5)來對仍舊活躍的walkDir調用進行計數,另一goroutine會在計數器減爲零的時候將fileSizes這個channel關閉
然而这个程序还是会花上很长时间才会结束。无法对walkDir做行化处理没什么别的原因,非是因为磁盘系统并行限。下面这个第三版本的du会对每一walkDir的调用创建一新的goroutine。它使用sync.WaitGroup (§8.5)来对仍旧活跃的walkDir调用进行计数,另一goroutine会在计数器减为零的时候将fileSizes这个channel关闭
<u><i>gopl.io/ch8/du3</i></u>
```go
@@ -165,7 +165,7 @@ func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
}
```
於這個程序在高峯期會創建成百上的goroutine需要改dirents函,用計數信號量來阻止他同時打開太多的文件,就像我在8.7中的併發爬蟲一樣
于这个程序在高峰期会创建成百上的goroutine需要改dirents函,用计数信号量来阻止他同时打开太多的文件,就像我在8.7中的并发爬虫一样
```go
// sema is a counting semaphore for limiting concurrency in dirents.
@@ -178,6 +178,6 @@ func dirents(dir string) []os.FileInfo {
// ...
```
這個版本比之前那快了好倍,管其具效率是和你的運行環境,器配置相
这个版本比之前那快了好倍,管其具效率是和你的运行环境,器配置相
**練習 8.9** 編寫一個du工具每隔一段時間將root目下的目大小計算併顯示出
**练习 8.9** 编写一个du工具每隔一段时间将root目下的目大小计算并显示出

View File

@@ -1,14 +1,14 @@
## 8.9. 併發的退出
## 8.9. 并发的退出
候我需要通知goroutine停止它正在的事情,比如一正在執行計算的web服,然而它的客端已經斷開了和服端的接。
候我需要通知goroutine停止它正在的事情,比如一正在执行计算的web服,然而它的客端已经断开了和服端的接。
Go語言併沒有提供在一goroutine中止另一goroutine的方法於這樣會導致goroutine之的共享量落在未定義的狀態上。在8.7中的rocket launch程序中往名字叫abort的channel里送了一個簡單的值在countdown的goroutine中會把這個值理解自己的退出信。但是如果我想要退出兩個或者任意多goroutine怎麽辦呢?
Go语言并没有提供在一goroutine中止另一goroutine的方法于这样会导致goroutine之的共享量落在未定义的状态上。在8.7中的rocket launch程序中往名字叫abort的channel里送了一个简单的值在countdown的goroutine中会把这个值理解自己的退出信。但是如果我想要退出两个或者任意多goroutine怎么办呢?
可能的手段是向abort的channel里送和goroutine目一多的事件退出它。如果些goroutine中已有一些自己退出了,那麽會導致我的channel里的事件比goroutine多,這樣導致我們的發送直接被阻塞。另一方面,如果些goroutine又生成了其它的goroutine的channel里的目又太少了所以有些goroutine可能會無法接收到退出消息。一般情下我是很知道在某一個時刻具有多少goroutine在行着的。另外,當一個goroutineabort channel中接收到一值的候,他會消費掉這個值,這樣其它的goroutine就法看到這條信息。了能夠達到我退出goroutine的目的需要更靠的策略,來通過一個channel把消息播出去,這樣goroutine們能夠看到這條事件消息,且在事件完成之,可以知道件事已經發生過了。
可能的手段是向abort的channel里送和goroutine目一多的事件退出它。如果些goroutine中已有一些自己退出了,那么会导致我的channel里的事件比goroutine多,这样导致我们的发送直接被阻塞。另一方面,如果些goroutine又生成了其它的goroutine的channel里的目又太少了所以有些goroutine可能会无法接收到退出消息。一般情下我是很知道在某一个时刻具有多少goroutine在行着的。另外,当一个goroutineabort channel中接收到一值的候,他会消费掉这个值,这样其它的goroutine就法看到这条信息。了能够达到我退出goroutine的目的需要更靠的策略,来通过一个channel把消息广播出去,这样goroutine们能够看到这条事件消息,且在事件完成之,可以知道件事已经发生过了。
迴憶一下我們關閉了一channel且被消掉了所有已送的值操作channel之的代可以立卽被執行,併且會産生零值。我可以將這個機製擴展一下,來作爲我們的廣播機製不要向channel送值,而是用關閉一個channel來進行廣播。
回忆一下我们关闭了一channel且被消掉了所有已送的值操作channel之的代可以立即被执行,并且会产生零值。我可以将这个机制扩展一下,来作为我们的广播机制不要向channel送值,而是用关闭一个channel来进行广播。
要一些小改,我就可以把退出邏輯加入到前一的du程序。首先們創建一退出的channel這個channel不向其中送任何值,但其所在的閉包內要寫明程序需要退出。我們同時還定義了一工具函cancelled這個函數在被調用的時候會輪詢退出狀態
要一些小改,我就可以把退出逻辑加入到前一的du程序。首先们创建一退出的channel这个channel不向其中送任何值,但其所在的闭包内要写明程序需要退出。我们同时还定义了一工具函cancelled这个函数在被用的时候会轮询退出状态
<u><i>gopl.io/ch8/du4</i></u>
```go
@@ -24,7 +24,7 @@ func cancelled() bool {
}
```
下面我們創建一個從標準輸入流中讀取內容的goroutine是一個比較典型的接到端的程序。每當有輸入被到(比如用按了迴車鍵),這個goroutine就把取消消息通過關閉done的channel播出去。
下面我们创建一个从标准输入流中读取内容的goroutine是一个比较典型的接到端的程序。每当有输入被到(比如用按了回车键),这个goroutine就把取消消息通过关闭done的channel广播出去。
```go
// Cancel traversal when input is detected.
@@ -34,7 +34,7 @@ go func() {
}()
```
在我需要使我的goroutine來對取消進行響應。在main goroutine中添加了select的第三case句,嚐試從done channel中接收容。如果這個case被滿足的在select到的時候卽會返迴,但在束之前我需要把fileSizes channel中的容“排”空在channel被關閉之前,舍掉所有值。這樣可以保證對walkDir的調用不要被向fileSizes送信息阻塞住,可以正地完成。
在我需要使我的goroutine来对取消进行响应。在main goroutine中添加了select的第三case句,尝试从done channel中接收容。如果这个case被足的在select到的时候即会返回,但在束之前我需要把fileSizes channel中的容“排”空在channel被关闭之前,舍掉所有值。这样可以保证对walkDir的用不要被向fileSizes送信息阻塞住,可以正地完成。
```go
for {
@@ -51,7 +51,7 @@ for {
}
```
walkDir這個goroutine一啟動就會輪詢取消狀態,如果取消狀態被設置的話會直接返迴,併且不做外的事情。這樣我們將所有在取消事件之後創建的goroutine改變爲無操作。
walkDir这个goroutine一启动就会轮询取消状态,如果取消状态被设置的话会直接返回,并且不做外的事情。这样我们将所有在取消事件之后创建的goroutine改变为无操作。
```go
func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
@@ -65,9 +65,9 @@ func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
}
```
在walkDir函的循中我們對取消狀態進行輪詢可以帶來明顯的益,可以避免在取消事件發生時還去創建goroutine。取消本身是有一些代的;想要快速的響應需要程序邏輯進行侵入式的改。保在取消生之不要有代太大的操作可能需要改你代里的很多地方,但是在一些重要的地方去檢査取消事件也確實能帶來很大的好
在walkDir函的循中我们对取消状态进行轮询可以带来明显的益,可以避免在取消事件发生时还去创建goroutine。取消本身是有一些代的;想要快速的响应需要程序逻辑进行侵入式的改。保在取消生之不要有代太大的操作可能需要改你代里的很多地方,但是在一些重要的地方去检查取消事件也确实能带来很大的好
對這個程序的一個簡單的性能分析可以揭示瓶在dirents函數中獲取一個信號量。下面的select可以讓這種操作可以被取消,且可以取消的延遲從幾百毫秒降低到十毫秒。
对这个程序的一个简单的性能分析可以揭示瓶在dirents函数中获取一个信号量。下面的select可以让这种操作可以被取消,且可以取消的延迟从几百毫秒降低到十毫秒。
```go
func dirents(dir string) []os.FileInfo {
@@ -81,8 +81,8 @@ func dirents(dir string) []os.FileInfo {
}
```
現在當取消發生時,所有台的goroutine都迅速停止且主函數會返迴。當然,主函數返迴時,一程序退出,而我們又無法在主函退出的時候確認其已經釋放了所有的源(譯註:因程序都退出了,你的代碼都沒法執行了)。里有一方便的竅門我們可以一用:取代掉直接主函數返迴,我們調用一panicruntime把每一goroutine的dump下。如果main goroutine是唯一一剩下的goroutine的,他清理掉自己的一切源。但是如果有其它的goroutine有退出,他可能沒辦法被正地取消掉,也有可能被取消但是取消操作很花時間;所以里的一個調研還是很有必要的。我用panic來獲取到足的信息來驗證我們上面的判,看看最到底是什麽樣的情
现在当取消发生时,所有台的goroutine都迅速停止且主函数会返回。当然,主函数返回时,一程序退出,而我们又无法在主函退出的时候确认其已经释放了所有的源(译注:因程序都退出了,你的代码都没法执行了)。里有一方便的窍门我们可以一用:取代掉直接主函数返回,我们调用一panicruntime把每一goroutine的dump下。如果main goroutine是唯一一剩下的goroutine的,他清理掉自己的一切源。但是如果有其它的goroutine有退出,他可能没办法被正地取消掉,也有可能被取消但是取消操作很花时间;所以里的一个调研还是很有必要的。我用panic来获取到足的信息来验证我们上面的判,看看最到底是什么样的情
**練習 8.10** HTTP求可能因http.Request結構體中Cancel channel的關閉而取消。改8.6中的web crawler支持取消http求。提示http.Get併沒有提供方便地定製一個請求的方法。你可以用http.NewRequest取而代之,置它的Cancel字段用http.DefaultClient.Do(req)來進行這個http求。)
**练习 8.10** HTTP求可能因http.Request结构体中Cancel channel的关闭而取消。改8.6中的web crawler支持取消http求。提示http.Get并没有提供方便地定制一个请求的方法。你可以用http.NewRequest取而代之,置它的Cancel字段用http.DefaultClient.Do(req)来进行这个http求。)
**練習 8.11** 接着8.4.4中的mirroredQuery流程實現一個併發請求url的fetch的變種。當第一個請求返迴時,直接取消其它的求。
**练习 8.11** 接着8.4.4中的mirroredQuery流程实现一个并发请求url的fetch的变种。当第一个请求返回时,直接取消其它的求。

View File

@@ -1,8 +1,8 @@
## 8.10. 示例: 聊天服
## 8.10. 示例: 聊天服
用一聊天服務器來終結本章節的內容,這個程序可以一些用戶通過服務器向其它所有用戶廣播文本消息。這個程序中有四goroutine。main和broadcaster各自是一goroutine例,每一個客戶端的接都有一handleConn和clientWriter的goroutine。broadcaster是select用法的不錯的樣例,因它需要理三不同型的消息。
用一聊天服务器来终结本章节的内容,这个程序可以一些用户通过服务器向其它所有用户广播文本消息。这个程序中有四goroutine。main和broadcaster各自是一goroutine例,每一个客户端的接都有一handleConn和clientWriter的goroutine。broadcaster是select用法的不错的样例,因它需要理三不同型的消息。
下面演示的main goroutine的工作是listen和accept(譯註:網絡編程里的概念)從客戶端過來的連接。每一個連接,程序都建立一新的handleConn的goroutine就像我在本章開頭的併發的echo服器里所做的那
下面演示的main goroutine的工作是listen和accept(译注:网络编程里的概念)从客户端过来的连接。每一个连接,程序都建立一新的handleConn的goroutine就像我在本章开头的并发的echo服器里所做的那
<u><i>gopl.io/ch8/chat</i></u>
```go
@@ -23,7 +23,7 @@ func main() {
}
```
是broadcaster的goroutine。他的內部變量clients會記録當前建立接的客端集合。其記録的內容是每一個客戶端的消息出channel的"格"信息。
是broadcaster的goroutine。他的内部变量clients会记录当前建立接的客端集合。其记录的内容是每一个客户端的消息出channel的"格"信息。
```go
type client chan<- string // an outgoing message channel
@@ -55,9 +55,9 @@ func broadcaster() {
}
```
broadcaster監聽來自全局的entering和leaving的channel來獲知客端的到來和離開事件。其接收到其中的一事件時,會更新clients集合當該事件是離開行爲時,它會關閉客戶端的消息出channel。broadcaster也會監聽全局的消息channel所有的客端都會向這個channel中送消息。broadcaster接收到什消息,就會將其廣播至所有接到服端的客端。
broadcaster监听来自全局的entering和leaving的channel来获知客端的到来和离开事件。其接收到其中的一事件时,会更新clients集合当该事件是离开行为时,它会关闭客户端的消息出channel。broadcaster也会监听全局的消息channel所有的客端都会向这个channel中送消息。broadcaster接收到什消息,就会将其广播至所有接到服端的客端。
現在讓我們看看每一個客戶端的goroutine。handleConn函數會爲它的客戶端創建一消息出channel併通過entering channel通知客端的到。然後它會讀取客戶端發來的每一行文本,併通過全局的消息channel來將這些文本送出去,併爲每條消息帶上發送者的前綴來標明消息身份。當客戶端發送完畢後handleConn會通過leaving這個channel通知客端的離開併關閉連接。
现在让我们看看每一个客户端的goroutine。handleConn函数会为它的客户端创建一消息出channel并通过entering channel通知客端的到。然后它会读取客户端发来的每一行文本,并通过全局的消息channel来将这些文本送出去,并为每条消息带上发送者的前缀来标明消息身份。当客户端发送完毕后handleConn会通过leaving这个channel通知客端的离开并关闭连接。
```go
func handleConn(conn net.Conn) {
@@ -87,9 +87,9 @@ func clientWriter(conn net.Conn, ch <-chan string) {
}
```
另外handleConn每一個客戶端創建了一clientWriter的goroutine接收向客戶端發出消息channel中送的播消息,併將它們寫入到客端的網絡連接。客端的取方循環會在broadcaster接收到leaving通知併關閉了channel後終止。
另外handleConn每一个客户端创建了一clientWriter的goroutine接收向客户端发出消息channel中送的广播消息,并将它们写入到客端的网络连接。客端的取方循环会在broadcaster接收到leaving通知并关闭了channel后终止。
下面演示的是當服務器有兩個活動的客戶端連接,且在兩個窗口中行的情使用netcat聊天:
下面演示的是当服务器有两个活动的客户端连接,且在两个窗口中行的情使用netcat聊天:
```
$ go build gopl.io/ch8/chat
@@ -113,12 +113,12 @@ You are 127.0.0.1:64216 127.0.0.1:64216 has arrived
127.0.0.1:64211 has left”
```
當與n個客戶端保持聊天session時,這個程序有2n+2個併發的goroutine然而這個程序卻併不需要式的(§9.2)。clients這個map被限在了一個獨立的goroutine中broadcaster所以它不能被併發地訪問。多goroutine共享的變量隻有這些channel和net.Conn的例,兩個東西都是併發安全的。我們會在下一章中更多地解決約束,併發安全以及goroutine中共享量的含
当与n个客户端保持聊天session时,这个程序有2n+2个并发的goroutine然而这个程序却并不需要式的(§9.2)。clients这个map被限在了一个独立的goroutine中broadcaster所以它不能被并发地访问。多goroutine共享的变量只有这些channel和net.Conn的例,两个东西都是并发安全的。我们会在下一章中更多地解决约束,并发安全以及goroutine中共享量的含
**練習 8.12** 使broadcaster能夠將arrival事件通知前所有的客端。爲了達成這個目的,你需要有一個客戶端的集合,且在entering和leaving的channel中記録客戶端的名字。
**练习 8.12** 使broadcaster能够将arrival事件通知前所有的客端。为了达成这个目的,你需要有一个客户端的集合,且在entering和leaving的channel中记录客户端的名字。
**練習 8.13** 使聊天服器能夠斷開空閒的客戶端連接,比如最近五分鐘之後沒有發送任何消息的那些客端。提示可以在其它goroutine中調用conn.Close()解除Read調就像input.Scanner()所做的那
**练习 8.13** 使聊天服器能够断开空闲的客户端连接,比如最近五分钟之后没有发送任何消息的那些客端。提示可以在其它goroutine中用conn.Close()解除Read就像input.Scanner()所做的那
**練習 8.14** 改聊天服器的網絡協議這樣每一個客戶端就可以在entering可以提供它的名字。消息前由之前的網絡地址改爲這個名字。
**练习 8.14** 改聊天服器的网络协议这样每一个客户端就可以在entering可以提供它的名字。消息前由之前的网络地址改为这个名字。
**練習 8.15** 如果一個客戶端沒有及時地讀取數據可能會導致所有的客端被阻塞。改broadcaster來跳過一條消息,而不是等待這個客戶端一直到其準備好寫。或者每一個客戶端的消息出channel建立緩衝區,這樣大部分的消息便不會被丟broadcaster應該用一非阻塞的send向這個channel中消息。
**练习 8.15** 如果一个客户端没有及时地读取数据可能会导致所有的客端被阻塞。改broadcaster来跳过一条消息,而不是等待这个客户端一直到其准备好写。或者每一个客户端的消息出channel建立缓冲区,这样大部分的消息便不会被丢broadcaster应该用一非阻塞的send向这个channel中消息。

View File

@@ -1,7 +1,7 @@
# 第八章 Goroutines和Channels
併發程序指同時進行多個任務的程序,着硬件的展,併發程序得越越重要。Web服務器會一次理成韆上萬的請求。平闆電腦和手app在渲染用戶畵面同時還會後台執行各種計算任務和網絡請求。使是傳統的批處理問題--讀取數據,計算,寫輸出--在也會用併發來隱藏掉I/O的操作延以充分利用現代計算機設備的多核心。計算機的性能每年都在以非性的速度增
并发程序指同时进行多个任务的程序,着硬件的展,并发程序得越越重要。Web服务器会一次理成千上万的请求。平板电脑和手app在渲染用户画面同时还会后台执行各种计算任务和网络请求。使是传统的批处理问题--读取数据,计算,写输出--在也会用并发来隐藏掉I/O的操作延以充分利用现代计算机设备的多核心。计算机的性能每年都在以非线性的速度增
Go言中的併發程序可以用兩種手段來實現。本章解goroutine和channel其支持“序通信程”(communicating sequential processes)或被簡稱爲CSP。CSP是一種現代的併發編程模型,在這種編程模型中值在不同的運行實例(goroutine)中傳遞,盡管大多數情況下仍然是被限製在單一實例中。第9章覆蓋更爲傳統的併發模型:多程共享存,如果你在其它的主流言中寫過併發程序的可能更熟悉一些。第9章也深入介一些併發程序帶來的風險和陷阱。
Go言中的并发程序可以用两种手段来实现。本章解goroutine和channel其支持“序通信程”(communicating sequential processes)或被简称为CSP。CSP是一种现代的并发编程模型,在这种编程模型中值在不同的运行实例(goroutine)中传递,尽管大多数情况下仍然是被限制在单一实例中。第9章覆盖更为传统的并发模型:多线程共享存,如果你在其它的主流言中写过并发程序的可能更熟悉一些。第9章也深入介一些并发程序带来的风险和陷阱。
管Go對併發的支持是衆多強力特性之一,但跟蹤調試併發程序是很睏難,在性程序中形成的直往往還會使我們誤入歧途。如果這是讀者第一次接觸併發,推稍微多花一些時間來思考這兩個章節中的例。
管Go对并发的支持是众多强力特性之一,但跟踪调试并发程序是很困难,在线性程序中形成的直往往还会使我们误入歧途。如果这是读者第一次接触并发,推稍微多花一些时间来思考这两个章节中的例。