Golang學習筆記:體驗Go的並發編程(二)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

在Go指南中,最後一節的練習是一個WEB爬蟲。剛開始看目錄以為真的是要寫一個爬蟲,直到仔細閱讀了代碼才發現,只是讓利用channel和mutex類比一個爬蟲的代碼。

初始狀態如下

package mainimport ("fmt")type Fetcher interface {// Fetch 返回 URL 的 body 內容,並且將在這個頁面上找到的 URL 放到一個 slice 中。Fetch(url string) (body string, urls []string, err error)}// Crawl 使用 fetcher 從某個 URL 開始遞迴的爬取頁面,直到達到最大深度。func Crawl(url string, depth int, fetcher Fetcher) {// TODO: 並行的抓取 URL。// TODO: 不重複抓取頁面。        // 下面並沒有實現上面兩種情況:if depth <= 0 {return}body, urls, err := fetcher.Fetch(url)if err != nil {fmt.Println(err)return}fmt.Printf("found: %s %q\n", url, body)for _, u := range urls {Crawl(u, depth-1, fetcher)}return}func main() {Crawl("http://golang.org/", 4, fetcher)}// fakeFetcher 是返回若干結果的 Fetcher。type fakeFetcher map[string]*fakeResulttype fakeResult struct {body stringurls []string}func (f fakeFetcher) Fetch(url string) (string, []string, error) {if res, ok := f[url]; ok {return res.body, res.urls, nil}return "", nil, fmt.Errorf("not found: %s", url)}// fetcher 是填充後的 fakeFetcher。var fetcher = fakeFetcher{"http://golang.org/": &fakeResult{"The Go Programming Language",[]string{"http://golang.org/pkg/","http://golang.org/cmd/",},},"http://golang.org/pkg/": &fakeResult{"Packages",[]string{"http://golang.org/","http://golang.org/cmd/","http://golang.org/pkg/fmt/","http://golang.org/pkg/os/",},},"http://golang.org/pkg/fmt/": &fakeResult{"Package fmt",[]string{"http://golang.org/","http://golang.org/pkg/",},},"http://golang.org/pkg/os/": &fakeResult{"Package os",[]string{"http://golang.org/","http://golang.org/pkg/",},},}

fetcher在初始化時就已經將爬蟲的爬取資訊寫死了,教程所要實現的是:並行爬取和同步控制

對於並行爬取,我的思路是每層根據寬度建立相應的線程進行遞迴爬取。實現起來比較簡單就是在for迴圈中建立線程遞迴執行Crawl函數

同步控制根據提示知道利用sync.Mutex做個訊號量,更新與查詢路徑表的時候保證同步即可。

實現的代碼如下

package mainimport ("fmt""sync")type Fetcher interface {// Fetch 返回 URL 的 body 內容,並且將在這個頁面上找到的 URL 放到一個 slice 中。Fetch(url string) (body string, urls []string, err error)}type walk struct {m   map[string]intmux sync.Mutex}// Crawl 使用 fetcher 從某個 URL 開始遞迴的爬取頁面,直到達到最大深度。func Crawl(url string, depth int, fetcher Fetcher) {if depth <= 0 {return}var wg = sync.WaitGroup{}var body stringvar urls []stringvar err error//Lockhaswalk.mux.Lock()if _, ok := haswalk.m[url]; !ok {body,urls,err=fetcher.Fetch(url)haswalk.m[url] = 1if err != nil {fmt.Println(err)haswalk.mux.Unlock()return}fmt.Printf("found: %s %q\n", url, body)}else{haswalk.mux.Unlock()return}haswalk.mux.Unlock()for _, u := range urls {//增加一條等待線程wg.Add(1)go func(target string,d int,fet Fetcher) {//遞迴爬取Crawl(target, d, fet)wg.Done()}(u,depth-1,fetcher)}wg.Wait()return}func main() {Crawl("http://golang.org/", 4, fetcher)}// fakeFetcher 是返回若干結果的 Fetcher。type fakeFetcher map[string]*fakeResulttype fakeResult struct {body stringurls []string}func (f fakeFetcher) Fetch(url string) (string, []string, error) {if res, ok := f[url]; ok {return res.body, res.urls, nil}return "", nil, fmt.Errorf("not found: %s", url)}//抓取路徑var haswalk = walk{m: make(map[string]int)}// fetcher 是填充後的 fakeFetcher。var fetcher = fakeFetcher{"http://golang.org/": &fakeResult{"The Go Programming Language",[]string{"http://golang.org/pkg/","http://golang.org/cmd/",},},"http://golang.org/pkg/": &fakeResult{"Packages",[]string{"http://golang.org/","http://golang.org/cmd/","http://golang.org/pkg/fmt/","http://golang.org/pkg/os/",},},"http://golang.org/pkg/fmt/": &fakeResult{"Package fmt",[]string{"http://golang.org/","http://golang.org/pkg/",},},"http://golang.org/pkg/os/": &fakeResult{"Package os",[]string{"http://golang.org/","http://golang.org/pkg/",},},}

完成這段代碼花費了半天的時間(真心太渣了T_T),其中有兩個坑,坑了我好久。

首先是,主線程提前結束,導致整個程式只爬取了一層後退出。

解決方案也很簡單,利用WaitGroup實現訊號量機制,沒建立一個新線程就增加一條訊號量記錄,當前線程在所有建立的線程都執行完畢後才會結束。

其次是,在並行爬取的過程中,剛開始的的寫法是

for _, u := range urls {//增加一條等待線程wg.Add(1)go func() {//遞迴爬取Crawl(u, depth-1, fetcher)wg.Done()}()}

看上去沒什麼問題,但是執行過程中發現,參數u的值,按道理來說應該是urls中的每條記錄,但是實際執行時,每次u都是urls中的最後一條記錄。

我猜測應該是子線程在擷取u的值的時候,u已經遍曆到了最後一條記錄,所以每次取都只會是最後一條記錄的結果。

寫法改成

for _, u := range urls {//增加一條等待線程wg.Add(1)go func(target string,d int,fet Fetcher) {//遞迴爬取Crawl(target, d, fet)wg.Done()}(u,depth-1,fetcher)}wg.Wait()

goroutine執行的函數增加三個參數後,先將參數傳過去,就正常了。

總算是完成了Go指南中的練習了!!!Go的文法和C還是挺像的,理解起來不算太難。在並發編程這裡,感覺自己的思想還是有問題,太稚嫩!寫這個假爬蟲的過程發現了好多前人踩過的坑我還在踩,各種死結、同步的問題其實都可以避免的~~~

寫這段代碼用的三idea的goland,在調試過程中發現斷點只對當前線程有用,沒有辦法設定成將所有線程都掛起T_T,如果有大神知道怎麼設定,跪求!!!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.