context使用

1. 概述

  go語言中goroutine之間的關聯關係,缺少維護,在erlang中有專門的機制來保障新開協程的生命週期,在go語言中,只能經過channel + select來實現,但不夠直觀,很繞。
  Context 一般被譯做 上下文 ,它是一個比較抽象的概念,通常理解爲程序單元的一個運行狀態、現場、快照。上下上下則是存在上下層的傳遞, 上 會把內容傳遞給 下 。
  在Go語言中,程序單元也就指的是Goroutine。context 包不只實現了在程序單元之間共享狀態變量的方法,同時能經過簡單的方法,使咱們在被調用程序單元的外部,經過設置ctx變量值,將過時撤銷信號傳遞給被調用的程序單元。html

  在go服務器中(http服務器),對於每一個請求的request都是在單獨的goroutine中進行的,處理一個request也可能涉及多個goroutine之間的交互, 使用context可使開發者方便地在這些goroutine裏傳遞request相關的數據、取消goroutine的signal或截止時間golang

  Done 方法在Context被取消或超時時返回一個close的channel,close的channel能夠做爲廣播通知,告訴給context相關的函數要中止當前工做而後返回。安全

  當一個父operation啓動一個goroutine用於子operation,這些子operation不可以取消父operation。下面描述的WithCancel函數提供一種方式能夠取消新建立的Context.服務器

  Context能夠安全的被多個goroutine使用。開發者能夠把一個Context傳遞給任意多個goroutine而後cancel這個context的時候就可以通知到全部的goroutine。ide

Err方法返回context爲何被取消。函數

Deadline返回context什麼時候會超時。協程

Value返回context相關的數據。htm

須要注意的就是 調用CancelFunc會取消child以及child生成的context,取出父context對這個child的引用,中止相關的計數器blog

實戰:生命週期

  1. context.Background 只應用在最高等級,做爲全部派生 context 的根。
  2. context.TODO 應用在不肯定要使用什麼的地方,或者當前函數之後會更新以便使用 context。
  3. context 取消是建議性的,這些函數可能須要一些時間來清理和退出。
  4. context.Value 應該不多使用,它不該該被用來傳遞可選參數。這使得 API 隱式的而且能夠引發錯誤。取而代之的是,這些值應該做爲參數傳遞。
  5. 不要將 context 存儲在結構中,要在函數中顯式傳遞它們,最好是做爲第一個參數。
  6. 永遠不要傳遞不存在的 context 。相反,若是您不肯定使用什麼,使用一個 ToDo context。
  7. Context 結構沒有取消方法,由於只有派生 context 的函數才能夠取消 context。

參考示例1:

package main

import (
    "context"
    "log"
    "os"
    "time"
)

var logg *log.Logger

func someHandler() {
    ctx, cancel := context.WithCancel(context.Background())
    go doStuff(ctx)

//10秒後取消doStuff
    time.Sleep(10 * time.Second)
    cancel()

}

//每1秒work一下,同時會判斷ctx是否被取消了,若是是就退出
func doStuff(ctx context.Context) {
    for {
        time.Sleep(1 * time.Second)
        select {
        case <-ctx.Done():
            logg.Printf("done")
            return
        default:
            logg.Printf("work")
        }
    }
}

func main() {
    logg = log.New(os.Stdout, "", log.Ltime)
    someHandler()
    logg.Printf("down")
        time.Sleep(10 * time.Second)
}

  輸出

18:00:17 work
18:00:18 work
18:00:19 work
18:00:20 work
18:00:21 work
18:00:22 work
18:00:23 work
18:00:24 work
18:00:25 work
18:00:26 down
18:00:26 done

  參考示例2:

package main

import (
    "context"
    "log"
    "os"
    "time"
)

var logg *log.Logger

func doTimeOutStuff(ctx context.Context) {
    for {
        time.Sleep(1 * time.Second)

        if deadline, ok := ctx.Deadline(); ok { //設置了deadl
            logg.Printf("deadline set")
            if time.Now().After(deadline) {
                logg.Printf(ctx.Err().Error())
                return
            }

        }

        select {
        case <-ctx.Done():
            logg.Printf("done")
            return
        default:
            logg.Printf("work")
        }
    }
}

func timeoutHandler() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    // ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
    go doTimeOutStuff(ctx)
    // go doStuff(ctx)

    time.Sleep(10 * time.Second)

    cancel()

}


func main() {
    logg = log.New(os.Stdout, "", log.Ltime)
    timeoutHandler()
    logg.Printf("down")
	time.Sleep(10 * time.Second)
}

  輸出:

18:04:37 deadline set
18:04:37 work
18:04:38 deadline set
18:04:38 work
18:04:39 deadline set
18:04:39 work
18:04:40 deadline set
18:04:40 work
18:04:41 deadline set
18:04:41 context deadline exceeded
18:04:46 down

  參考連接:

https://studygolang.com/articles/12566

https://www.cnblogs.com/zhangboyu/p/7456606.html

https://studygolang.com/articles/13866?fr=sidebar

相關文章
相關標籤/搜索