07 - 獲取URL, 併發 goroutine和channel
Go 語言最大的特徵之一,就是對併發程式設計的支援,接下來我們將簡單理解一下併發
一、goroutine 獲取URL
06 - 獲取URL 中簡單地獲取了 http 的資訊,接下來我們將會同時去獲取所有的 URL ,所以這個程式的總執行時間不會超過執行時間最長的那一個任務,前面的 urlfetch 程式執行時間則是所有任務執行時間之和。 urlfetchall 程式只會列印獲取的內容大小和經過的時間,不會像之前那樣列印獲取的內容。
相關原始碼如下:
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
func main() {
start := time.Now()
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch)
}
for range os.Args[1:] {
fmt.Println(<-ch)
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
func fetch( url string, ch chan<- string) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err)
return
}
//ioutil.Discard 為io.Writer 型別,是類似於/dev/null
// 像一個無底洞
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
if err != nil {
ch <- fmt.Sprintf("while reading %s: %v", url, err)
return
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
執行路徑:
$GOPATH/src/gopl/ch1/urlfetch2
執行命令:
$ go run urlfetchall.go https://www.baidu.com https://www.qq.com https://www.sina.com.cn
執行結果:
程式分析
1. goroutinue
goroutinue ,本質上就是協程。但有兩點不同:
- goroutinue 可以實現並行,也就是說,多個協程可以在多個處理器同時跑。而協程同一時刻只能在一個處理器上跑(把宿主語言想象成單執行緒的就好了)。
- goroutinue 之間的通訊是通過 channel ,而協程的通訊是通過 yield 和 resume() 操作。
- 執行 goroutinue ,只需要在函式的呼叫前面加一個 go
go doingSth()
2. channel
在 java 的世界裡,併發主要是靠鎖住臨界資源(共享記憶體)來保證同步的。而 channel 則是 goroutinues 之間進行通訊的利器。
channel 可以形象比喻為工廠裡的傳送帶,一頭的生產者 goroutine 往傳輸帶放東西,另一頭的消費者 goroutinue 則從輸送帶取東西。 channel 實際上是一個 有型別的訊息佇列 ,遵循先進先出的特點。
-
channel的操作符號
ch <- ele 表示ele被髮送給channel ch;
ele2 <- ch 表示從channel ch取一個值,然後賦給ele2 -
阻塞式channel
channel預設是沒有緩衝區的,沒有buff的channel只能容納一個元素,也就是說,通訊是阻塞的。send操作必須等到有消費者accept才算完成。
-
帶有buff的channel
帶有buff的channel則可以非阻塞容納N個元素。傳送資料到buffed channel不會被阻塞,除非channel已滿;同樣的,從buffed channel取資料也不會被阻塞,除非channel空了。這有點像java的ConcurrentLinkedQueue。
3. io.Discard
它是一個 io.Writer 型別的變數, 是一個類似於 /dev/null 的無底洞
總結
- goroutinue 和 channel 的簡單理解, 後續併發程式設計將會更加詳細的學習
- io.Discard 的理解
- 練習: 找一個數據量比較大的網站,用本小節中的程式調研網站的快取策略,對每個URL執行兩遍請求,檢視兩次時間是否有較大的差別,並且每次獲取到的響應內容是否一致,修改本節中的程式,將響應結果輸出,以便於進行對比。 原始碼路徑:$GOPATH/src/gopl/ch1/urlfetch2