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是一個接口,具體的內容以下:
~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使用原則

  1. 不要把Context放在結構體中,要以參數的方式進行傳遞
  2. 以Context做爲參數的函數方法,應該把Context做爲第一個參數,放在第一位
  3. 給一個函數方法傳遞Context的時候,不要傳遞nil,若是不知道傳遞什麼,就使用context.TODO
  4. Context的Value相關方法應該傳遞必須的數據,不要什麼數據都使用這個傳遞
相關文章
相關標籤/搜索