控制併發有兩種經典的方式,一種是WaitGroup,另外一種就是Context WaitGroup的使用 WaitGroup可以用來控制多個goroutine同時完成 ~~~go func main() { var wg sync.WaitGroup wg.Add(2) go func() { ti ...
控制併發有兩種經典的方式,一種是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是一個介面,具體的內容如下:
~go~
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使用原則
- 不要把Context放在結構體中,要以參數的方式進行傳遞
- 以Context作為參數的函數方法,應該把Context作為第一個參數,放在第一位
- 給一個函數方法傳遞Context的時候,不要傳遞nil,如果不知道傳遞什麼,就使用context.TODO
- Context的Value相關方法應該傳遞必須的數據,不要什麼數據都使用這個傳遞