go Context的使用


控制並發有兩種經典的方式,一種是WaitGroup,另外一種就是Context

WaitGroup的使用

  • WaitGroup可以用來控制多個goroutine同時完成
	func main() {
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		time.Sleep(2*time.Second)
		fmt.Println("1號完成")
		wg.Done()
	}()
	go func() {
		time.Sleep(2*time.Second)
		fmt.Println("2號完成")
		wg.Done()
	}()
	wg.Wait()
	fmt.Println("好了,大家都干完了,放工")
}
以上例子一定要等到兩個goroutine同時做完才會全部完成,這種控制並發方式尤其適用於多個goroutine協同做一件事情的時候。

chan通知

  • chan也可以用於控制goroutine,通過chan來控制goroutine是否結束
	func main() {
	stop := make(chan bool)

	go func() {
		for {
			select {
			case <-stop:
				fmt.Println("監控退出,停止了...")
				return
			default:
				fmt.Println("goroutine監控中...")
				time.Sleep(2 * time.Second)
			}
		}
	}()

	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知監控停止")
	stop<- true
	//為了檢測監控過是否停止,如果沒有監控輸出,就表示停止了
	time.Sleep(5 * time.Second)
}
例子中我們通過select判斷stop是否接受到值,如果接受到值就表示可以推出停止了,如果沒有接受到,就會執行default里面的監控邏輯,繼續監控,直到收到stop的通知

以上控制goroutine的方式在大多數情況下可以滿足我們的使用,但是也存在很多局限性,比如有很多goroutiine,並且這些goroutine還衍生了其他goroutine,此時chan就比較困難解決這樣的問題了

Context

以上問題是存在的,比如一個網絡請求request,每個request都需要開啟一個goroutine做一些事情。所以我們需要一種可以跟蹤goroutine的方案才可以達到控制的目的,go為我們提供了Context

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go watch(ctx,"【監控1】")
	go watch(ctx,"【監控2】")
	go watch(ctx,"【監控3】")
	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知監控停止")
	cancel()
	//為了檢測監控過是否停止,如果沒有監控輸出,就表示停止了
	time.Sleep(5 * time.Second)
}
func watch(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(name,"監控退出,停止了...")
			return
		default:
			fmt.Println(name,"goroutine監控中...")
			time.Sleep(2 * time.Second)
		}
	}
}

例子中啟動了3個監控goroutine進行不斷的監控,每一個都使用Context進行跟蹤,當我們使用cancel函數通知取消時候,這3個 goroutine都會被結束。所有基於這個context或者衍生出來的子Context都會收到通知,這樣就可以進行清理操作最終釋放goroutine了

Context接口

Context是一個接口,具體的內容如下:

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key interface{}) interface{}
}
  • Deadline方法是獲取設置的截止時間的意思,第一個返回式是截止時間,到了這個時間點,Context會自動發起取消請求;第二個返回值ok==false時表示沒有設置截止時間,如果需要取消的話,需要調用取消函數進行取消
  • Done方法返回一個只讀的chan,類型為struct{},我們在goroutine中,如果該方法返回的chan可以讀取,則意味着parent context已經發起了取消請求,我們通過Done方法收到這個信號后,就應該做清理操作,然后退出goroutine,釋放資源
  • Err方法返回取消的錯誤原因,因為什么Context被取消。
  • Value方法獲取該Context上綁定的值,是一個鍵值對,所以要通過一個Key才可以獲取對應的值,這個值一般是線程安全的

Context的繼承衍生

  • context包為我們提供的With系列的函數了
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

這四個With函數,接收的都有一個partent參數,就是父Context,我們要基於這個父Context創建出子Context的意思

  • WithCancel函數,傳遞一個父Context作為參數,返回子Context,以及一個取消函數用來取消Context
  • WithDeadline函數,和WithCancel差不多,它會多傳遞一個截止時間參數,意味着到了這個時間點,會自動取消Context,當然我們也可以不等到這個時候,可以提前通過取消函數進行取消
  • WithTimeout和WithDeadline基本上一樣,這個表示是超時自動取消,是多少時間后自動取消Context的意思
  • WithValue函數和取消Context無關,它是為了生成一個綁定了一個鍵值對數據的Context,這個綁定的數據可以通過Context.Value方法訪問到

Context使用原則

  1. 不要把Context放在結構體中,要以參數的方式進行傳遞
  2. 以Context作為參數的函數方法,應該把Context作為第一個參數,放在第一位
  3. 給一個函數方法傳遞Context的時候,不要傳遞nil,如果不知道傳遞什么,就使用context.TODO
  4. Context的Value相關方法應該傳遞必須的數據,不要什么數據都使用這個傳遞


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM