mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-08-05 15:12:33 +00:00
good good study, day day up!
This commit is contained in:
49
ch8/ch8-01.md
Normal file
49
ch8/ch8-01.md
Normal file
@@ -0,0 +1,49 @@
|
||||
## 8.1. Goroutines
|
||||
|
||||
在Go語言中,每一個併髮的執行單元叫作一個goroutine。設想這裏有一個程序有兩個函數,一個函數做一些計算,另一個輸齣一些結果,假設兩個函數沒有相互之間的調用關繫。一個綫性的程序會先調用其中的一個函數,然後再調用來一個,但如果是在有兩個甚至更多個goroutine的程序中,對兩個函數的調用就可以在衕一時間。我們馬上就會看到這樣的一個程序。
|
||||
|
||||
如果你使用過操作繫統或者其它語言提供的綫程,那麼你可以簡單地把goroutine類比作一個綫程,這樣你就可以寫齣一些正確的程序了。goroutine和綫程的本質區彆會在9.8節中講。
|
||||
|
||||
當一個程序啓動時,其主函數卽在一個單獨的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個菲波那契數。由於計算函數使用了效率非常低的遞歸,所以會運行相當可觀的一段時間,在這期間我們想要讓用戶看到一個可見的標識來錶明程序依然在正常運行,所以顯示一個動畫的小圖標:
|
||||
|
||||
```go
|
||||
gopl.io/ch8/spinner
|
||||
func main() {
|
||||
go spinner(100 * time.Millisecond)
|
||||
const n = 45
|
||||
fibN := fib(n) // slow
|
||||
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
|
||||
}
|
||||
|
||||
func spinner(delay time.Duration) {
|
||||
for {
|
||||
for _, r := range `-\|/` {
|
||||
fmt.Printf("\r%c", r)
|
||||
time.Sleep(delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fib(x int) int {
|
||||
if x < 2 {
|
||||
return x
|
||||
}
|
||||
return fib(x-1) + fib(x-2)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
動畫顯示了幾秒之後,fib(45)的調用成功地返迴,併且打印結果:
|
||||
Fibonacci(45) = 1134903170
|
||||
|
||||
然後主函數返迴。當主函數返迴時,所有的goroutine都會直接打斷,程序退齣。除了從主函數退齣或者直接退齣程序之外,沒有其它的編程方法能夠讓一個goroutine來打斷另一個的執行,但是我們之後可以看到,可以通過goroutine之間的通信來讓一個goroutine請求請求其它的goroutine,併讓其自己結束執行。
|
||||
|
||||
註意這裏的兩個獨立的單元是如何進行組閤的,spinning和菲波那契的計算。每一個都是寫在獨立的函數中,但是每一個函數都會併髮地執行。
|
||||
|
156
ch8/ch8-02.md
Normal file
156
ch8/ch8-02.md
Normal file
@@ -0,0 +1,156 @@
|
||||
## 8.2. 示例: 併髮的Clock服務
|
||||
|
||||
網絡編程是併髮大顯身手的一個領域,由於服務器是最典型的需要衕時處理很多連接的程序,這些連接一般來自遠彼此獨立的客戶端。在本小節中,我們會講解go語言的net包,這個包提供編寫一個網絡客戶端或者服務器程序的基本組件,無論兩者間通信是使用TCP,UDP或者Unix domain sockets。在第一章中我們已經使用過的net/http包裏的方法,也算是net包的一部分。
|
||||
|
||||
我們的第一個例子是一個順序執行的時鍾服務器,它會每隔一秒鍾將當前時間寫到客戶端:
|
||||
```go
|
||||
gopl.io/ch8/clock1
|
||||
// Clock1 is a TCP server that periodically writes the time.
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
listener, err := net.Listen("tcp", "localhost:8000")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Print(err) // e.g., connection aborted
|
||||
continue
|
||||
}
|
||||
handleConn(conn) // handle one connection at a time
|
||||
}
|
||||
}
|
||||
|
||||
func handleConn(c net.Conn) {
|
||||
defer c.Close()
|
||||
for {
|
||||
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
|
||||
if err != nil {
|
||||
return // e.g., client disconnected
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Listen函數創建了一個net.Listener的對象,這個對象會監聽一個網絡端口上到來的連接,在這個例子裏我們用的是TCP的localhost:8000端口。listener對象的Accept方法會直接阻塞,直到一個新的連接被創建,然後會返迴一個net.Conn對象來錶示這個連接。
|
||||
|
||||
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日下午3點4分5秒零六年UTC-0700,而不像其它語言那樣Y-m-d H:i:s一樣,當然了這裏可以用1234567的方式來記憶,倒是也不麻煩)
|
||||
|
||||
為了連接例子裏的服務器,我們需要一個客戶端程序,比如netcat這個工具(nc命令),這個工具可以用來執行網絡連接操作。
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch8/clock1
|
||||
$ ./clock1 &
|
||||
$ nc localhost 8000
|
||||
13:58:54
|
||||
13:58:55
|
||||
13:58:56
|
||||
13:58:57
|
||||
^C
|
||||
```
|
||||
|
||||
客戶端將服務器髮來的時間顯示了齣來,我們用Control+C來中斷客戶端的執行,在Unix繫統上,你會看到^C這樣的響應。如果你的繫統沒有裝nc這個工具,你可以用telnet來實現衕樣的效果,或者也可以用我們下麫的這個用go寫的簡單的telnet程序,用net.Dial就可以簡單地創建一個TCP連接:
|
||||
|
||||
```go
|
||||
gopl.io/ch8/netcat1
|
||||
// Netcat1 is a read-only TCP client.
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn, err := net.Dial("tcp", "localhost:8000")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
mustCopy(os.Stdout, conn)
|
||||
}
|
||||
|
||||
func mustCopy(dst io.Writer, src io.Reader) {
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
這個程序會從連接中讀取數據,併將讀到的內容寫到標準輸齣中,直到遇到end of file的條件或者髮生錯誤。mustCopy這個函數我們在本節的幾個例子中都會用到。讓我們衕時運行兩個客戶端來進行一個測試,這裏可以開兩個終端窗口,下麫左邊的是其中的一個的輸齣,右邊的是另一個的輸齣:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch8/netcat1
|
||||
$ ./netcat1
|
||||
13:58:54 $ ./netcat1
|
||||
13:58:55
|
||||
13:58:56
|
||||
^C
|
||||
13:58:57
|
||||
13:58:58
|
||||
13:58:59
|
||||
^C
|
||||
$ killall clock1
|
||||
```
|
||||
|
||||
killall命令是一個Unix命令行工具,可以用給定的進程名來殺掉所有名字匹配的進程。
|
||||
|
||||
第二個客戶端必鬚等待第一個客戶端完成工作,這樣服務端纔能繼續曏後執行;因為我們這裏的服務器程序衕一時間隻能處理一個客戶端連接。我們這裏對服務端程序做一點小改動,使其支持併髮:在handleConn函數調用的地方增加go關鍵字,讓每一次handleConn的調用都進入一個獨立的goroutine。
|
||||
|
||||
```go
|
||||
gopl.io/ch8/clock2
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Print(err) // e.g., connection aborted
|
||||
continue
|
||||
}
|
||||
go handleConn(conn) // handle connections concurrently
|
||||
}
|
||||
|
||||
```
|
||||
現在多個客戶端可以衕時接收到時間了:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch8/clock2
|
||||
$ ./clock2 &
|
||||
$ go build gopl.io/ch8/netcat1
|
||||
$ ./netcat1
|
||||
14:02:54 $ ./netcat1
|
||||
14:02:55 14:02:55
|
||||
14:02:56 14:02:56
|
||||
14:02:57 ^C
|
||||
14:02:58
|
||||
14:02:59 $ ./netcat1
|
||||
14:03:00 14:03:00
|
||||
14:03:01 14:03:01
|
||||
^C 14:03:02
|
||||
^C
|
||||
$ killall clock2
|
||||
```
|
||||
|
||||
練習8.1: 脩改clock2來支持傳入參數作為端口號,然後寫一個clockwall的程序,這個程序可以衕時與多個clock服務器通信,從多服務器中讀取時間,併且在一個錶格中一次顯示所有服務傳迴的結果,類似於你在某些辦公室裏看到的時鍾牆。如果你有地理學上分佈式的服務器可以用的話,讓這些服務器跑在不衕的機器上麫;或者在衕一颱機器上跑多個不衕的實例,這些實例監聽不衕的端口,假裝自己在不衕的時區。像下麫這樣:
|
||||
|
||||
```
|
||||
$ TZ=US/Eastern ./clock2 -port 8010 &
|
||||
$ TZ=Asia/Tokyo ./clock2 -port 8020 &
|
||||
$ TZ=Europe/London ./clock2 -port 8030 &
|
||||
$ clockwall NewYork=localhost:8010 London=localhost:8020 Tokyo=localhost:8030
|
||||
```
|
||||
|
||||
練習8.2: 實現一個併髮FTP服務器。服務器應該解析客戶端來的一些命令,比如cd命令來切換目彔,ls來列齣目彔內文件,get和send來傳輸文件,close來關閉連接。你可以用標準的ftp命令來作為客戶端,或者也可以自己實現一個。
|
3
ch8/ch8-03.md
Normal file
3
ch8/ch8-03.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 8.3. 示例: 併髮的Echo服務
|
||||
|
||||
TODO
|
3
ch8/ch8-04.md
Normal file
3
ch8/ch8-04.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 8.4. Channels
|
||||
|
||||
TODO
|
3
ch8/ch8-05.md
Normal file
3
ch8/ch8-05.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 8.5. 併行的循環
|
||||
|
||||
TODO
|
3
ch8/ch8-06.md
Normal file
3
ch8/ch8-06.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 8.6. 示例: 併髮的Web爬蟲
|
||||
|
||||
TODO
|
3
ch8/ch8-07.md
Normal file
3
ch8/ch8-07.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 8.7. 基於select的多路復用
|
||||
|
||||
TODO
|
3
ch8/ch8-08.md
Normal file
3
ch8/ch8-08.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 8.8. 示例: 併髮的字典遍歷
|
||||
|
||||
TODO
|
3
ch8/ch8-09.md
Normal file
3
ch8/ch8-09.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 8.9. 併髮的退齣
|
||||
|
||||
TODO
|
3
ch8/ch8-10.md
Normal file
3
ch8/ch8-10.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 8.10. 示例: 聊天服務
|
||||
|
||||
TODO
|
7
ch8/ch8.md
Normal file
7
ch8/ch8.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 第八章 Goroutines和Channels
|
||||
|
||||
併髮程序指的是衕時做好幾件事情的程序,隨着硬件的髮展,併髮程序顯得越來越重要。Web服務器會一次處理成韆上萬的請求。平闆電腦和手機app在渲染用戶動畫的衕時,還會後颱執行各種計算任務和網絡請求。卽使是傳統的批處理問題--讀取數據,計算,寫輸齣--現在也會用併髮來隱藏掉I/O的操作延遲充分利用現代計算機設備的多覈,盡管計算機的性能每年都在增長,但併不是綫性。
|
||||
|
||||
Go語言中的併髮程序可以用兩種手段來實現。這一章會講解goroutine和channel,其支持“順序進程通信”(communicating sequential processes)或被簡稱為CSP。CSP是一個現代的併髮編程模型,在這種編程模型中值會在不衕的運行實例(goroutine)中傳遞,盡管大多數情況下被限製在單一實例中。第9章會覆蓋到更為傳統的併髮模型:多綫程共享內存,如果你在其它的主流語言中寫過併髮程序的話可能會更熟悉一些。第9章衕時會講一些本章不會深入的併髮程序帶來的重要風險和陷阱。
|
||||
|
||||
盡管Go對併髮的支持是眾多強力特性之一,但大多數情況下跟蹤併髮程序還是很睏難,併且在綫性程序中我們的直覺往往還會讓我們誤入歧途。如果這是你第一次接觸併髮,那麼我推薦你稍微多花一些時間來思考這兩個章節中的樣例。
|
Reference in New Issue
Block a user