GO併發特性

1.goquery 爬蟲庫

goquery的選擇器功能很強大,很好用。

go get github.com/PuerkitoBio/goquery




package main

import (
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/PuerkitoBio/goquery"
)

func fetch(url string) *goquery.Document {
	fmt.Println("Fetch Url", url)
	client := &http.Client{}
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal("Http get err:", err)
	}
	if resp.StatusCode != 200 {
		log.Fatal("Http status code:", resp.StatusCode)
	}
	defer resp.Body.Close()
	//將html body轉換成Document
	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return doc
}

func parseUrls(url string, ch chan bool) {
	doc := fetch(url)
	doc.Find("ol.grid_view li").Find(".hd").Each(func(index int, ele *goquery.Selection) {
		movieUrl, _ := ele.Find("a").Attr("href")
		fmt.Println(ele.Find(".title").Eq(0).Text(),movieUrl)
	})
	time.Sleep(2 * time.Second)
	ch <- true
}

func main() {
	start := time.Now()
	ch := make(chan bool)
	for i := 0; i < 10; i++ {
		//異步開啓線程
		go parseUrls("https://movie.douban.com/top250?start="+strconv.Itoa(25*i), ch)
	}

	for i := 0; i < 10; i++ {
		data :=<-ch
		fmt.Println("data:",data)
	}

	elapsed := time.Since(start)
	fmt.Printf("Took %s", elapsed)
}




2. 併發通道

chan 可以理解爲隊列,遵循先進先出的規則。

3. defer 函數

defer 函數大家肯定都用過,它在聲明時不會立刻去執行,而是在函數 return 後去執行的。 它的主要應用場景有異常處理、記錄日誌、清理數據、釋放資源 等等。

package main

import (
	"fmt"

)
func main() {
 //defer 函數大家肯定都用過,它在聲明時不會立刻去執行,而是在函數 return 後去執行的。
 //defer 函數定義的順序 與 實際執的行順序是相反的,也就是最先聲明的最後才執行。
	defer fmt.Println("1")
	defer fmt.Println("2")
	defer fmt.Println("3")

	fmt.Println("main")
}


4.常用併發操作

package goroutinue

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"
)

// 超時檢測機制
func GetWithTimeout(d time.Duration) (string, error) {
	ret := make(chan string)
	go func() {
		//do something
		time.Sleep(d)
		ret <- "Hello"
	}()
	select {
	case result := <-ret:
		return result, nil
	case <-time.After(time.Millisecond * 100):
		return "", errors.New("time out")
	}
}

// 當我們啓動多個任務時,如果有一個任務完成之後即可返回
// 例如:我們通過關鍵字去百度和谷歌搜索
// 當其中任意一個先拿到結果之後,我們就直接返回那一個
func GetOne() string {
	ret := make(chan string)
	for i := 0; i < 10; i++ {
		go func(i int) {
			ret <- fmt.Sprintf("%d : I get the result", i)
		}(i)
	}
	//當ret中一有結果,就直接返回了
	return <-ret
}

// 當所有任務執行完成之後,纔將結果返回
func GetAll() string {
	//開啓10個線程
	ret := make(chan string, 10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			ret <- fmt.Sprintf("%d : I get the result \n", i)
		}(i)
	}
	// 當所有結果拿到之後,組裝結果,返回
	result := ""
	for i := 0; i < 10; i++ {
		result += <-ret
	}
	return result
}

// 使用watiGroup實現獲取所有結果之後再返回
func GetAllWithGroup() string {

	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			fmt.Println(i,": I get the result ")
			wg.Done()
		}(i)
	}
	wg.Wait()
	result := ""
	return result
}

// 取消任務
func CancelTask() {
	//取消任務標識通道
	cancel := make(chan struct{})
	for i := 0; i < 5; i++ {
		go func(i int, cancel chan struct{}) {
			for {
				if isCancel(cancel) {
					break
				}
				time.Sleep(time.Millisecond * 5)
			}
			fmt.Println(i, ":Canceled")
		}(i, cancel)
	}
	//當關閉通道,那麼所有的goroutine都可以監聽到
	//如果僅僅是往這個通道發信息,那麼只會有一個goroutine被取消
	close(cancel)
	time.Sleep(time.Millisecond * 20)
}

// 判斷通道是否取消
func isCancel(cancel chan struct{}) bool {
	select {
	case <-cancel:
		return true
	default:
		return false
	}
}

// 使用上下文取消全部任務(一般這些任務分上下級的)
// 一個goroutine中又啓動了其他的goroutine
func CancelAllTask() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	for i := 0; i < 5; i++ {
		go func(i int, ctx context.Context) {
			for {
				if isCancelFunc(ctx) {
					break
				}
				time.Sleep(time.Millisecond * 5)
			}
			fmt.Println(i, ":Canceled")
		}(i, ctx)
	}
	//取消全部任務
	cancelFunc()
	time.Sleep(time.Millisecond * 20)
}

func isCancelFunc(ctx context.Context) bool {
	select {
	case <-ctx.Done():
		return true
	default:
		return false
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章