亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go語言并發編程基礎上下文概念是什么

發布時間:2022-08-08 16:19:57 來源:億速云 閱讀:137 作者:iii 欄目:開發技術

本篇內容介紹了“Go語言并發編程基礎上下文概念是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

    1 Go 中的 Context

    Golang 的上下文也是應用開發常用的并發控制工具。同理,上下文可以用于在程序中的 API 層或進程之間共享請求范圍的數據,除此之外,Go 的 Context 庫還提供取消信號(Cancel)以及超時機制(Timeout)。

    Context 又被稱為上下文,與 WaitGroup 不同的是,Context 對于派生 goroutine 有更強的控制力,可以管理多級的 goroutine。

    但我們在 Go 中創建一個 goroutine 時,如果發生了一個錯誤,并且這個錯誤永遠不會終止,而其他程序會繼續進行。加入有一個不被調用的 goroutine 運行無限循環,如下所示:

    package main
    import "fmt"
    func main() {
        dataCom := []string{"alex", "kyrie", "kobe"}
        go func(data []string) {
            // 模擬大量運算的死循環
        }(dataCom)
        // 其他代碼正常執行
        fmt.Println("剩下的代碼執行正常邏輯")
    }

    上面的例子并不完整,dataCom goroutine 可能會也可能不會成功處理數據。它可能會進入無限循環或導致錯誤。我們的其余代碼將不知道發生了什么。

    有多種方法可以解決這個問題。其中之一是使用通道向我們的主線程發送一個信號,表明這個 goroutine 花費的時間太長,應該取消它。

    package main
    import (
    	"fmt"
    	"time"
    )
    func main() {
    	stopChannel := make(chan bool)
    	dataCom := []string{"alex", "kyrie", "kobe"}
    	go func(stopChannel chan bool) {
    		go func(data []string) {
    			// 大量的計算
    		}(dataCom)
    		for range time.After(2 * time.Second) {
    			fmt.Println("此操作運行時間過長,取消中")
    			stopChannel <- true
    		}
    	}(stopChannel)
    	<-stopChannel
    	// 其他代碼正常執行
    	fmt.Println("剩下的代碼執行正常邏輯")
    }

    上面的邏輯很簡單。我們正在使用一個通道向我們的主線程發出這個 goroutine 花費的時間太長的信號。但是同樣的事情可以用 context 來完成,這正是 context 包存在的原因。

    package main
    import (
    	"context"
    	"fmt"
    	"time"
    )
    func main() {
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
    	defer cancel()
    	dataCom := []string{"alex", "kyrie", "kobe"}
    	go func() {
    		go func(data []string) {
    			// 大量的計算
    		}(dataCom)
    		for range time.After(2 * time.Second) {
    			fmt.Println("此操作運行時間過長,取消中")
    			cancel()
    			return
    		}
    	}()
    	select {
    	case <-ctx.Done():
    		fmt.Println("上下文被取消")
    	}
    }

    2 Context 接口

    Context 接口定義:

    type Context interface {
      Deadline() (deadline time.Time, ok bool)
      Done &lt;-chan struct{}
      Err() error
      Value(key interface{}) interface{}
    }

    Context 接口定義了 4 個方法:

    • Deadline(): 返回取消此上下文的時間 deadline(如果有)。如果未設置 deadline 時,則返回 ok==false,此時 deadline 為一個初始值的 time.Time 值。后續每次調用這個對象的 Deadline 方法時,都會返回和第一次調用相同的結果。

    • Done() : 返回一個用于探測 Context 是否取消的 channel,當 Context 取消會自動將該 channel 關閉,如果該 Context 不能被永久取消,該函數返回 nil。例如 context.Background();如果 Done 被 close,Err 方法會返回 Done 被 close 的原因。

    • Err(): 該方法會返回 context 被關閉的原因,關閉原因由 context 實現控制,不需要用戶設置;如果 Done() 尚未關閉,則 Err() 返回 nil

    • Value() : 在樹狀分布的 goroutine 之間共享數據,用 map 鍵值的工作方法,通過 key 值查詢 value。

    每次創建新上下文時,都會得到一個符合此接口的類型。上下文的真正實現隱藏在這個包和這個接口后面。這些是您可以創建的工廠類型的上下文:

    context.TODO

    context.Background

    context.WithCancel

    context.WithValue

    context.WithTimeout

    context.WithDeadline

    3 Context Tree

    在實際實現中,我們通常使用派生上下文。我們創建一個父上下文并將其傳遞到一個層,我們派生一個新的上下文,它添加一些額外的信息并將其再次傳遞到下一層,依此類推。通過這種方式,我們創建了一個從作為父級的根上下文開始的上下文樹。

    這種結構的優點是我們可以一次性控制所有上下文的取消。如果根信號關閉了上下文,它將在所有派生的上下文中傳播,這些上下文可用于終止所有進程,立即釋放所有內容。這使得上下文成為并發編程中非常強大的工具。

    Go語言并發編程基礎上下文概念是什么

    4 創建上下文

    4.1 上下文創建函數

    我們可以從現有的上下文中創建或派生上下文。頂層(根)上下文是使用 BackgroundTODO 方法創建的,而派生上下文是使用 WithCancel、WithDeadline、WithTimeout 或 WithValue 方法創建的。

    所有派生的上下文方法都返回一個取消函數 CancelFunc,但 WithValue 除外,因為它與取消無關。調用 CancelFunc 會取消子項及其子項,刪除父項對子項的引用,并停止任何關聯的計時器。調用 CancelFunc 失敗會泄漏子項及其子項,直到父項被取消或計時器觸發。

    • context.Background() ctx Context

    此函數返回一個空上下文。這通常只應在主請求處理程序或頂級請求處理程序中使用。這可用于為主函數、初始化、測試以及后續層或其他 goroutine 派生上下文的時候。

    ctx, cancel := context.Background()
    • context.TODO() ctx Context

    此函數返回一個非 nil 的、空的上下文。沒有任何值、不會被 cancel,不會超時,也沒有截止日期。但是,這也應該僅在您不確定要使用什么上下文或者該函數還不能用于接收上下文時,可以使用這個方法,并且將在將來需要添加時使用。

    ctx, cancel := context.TODO()
    • context.WithValue(parent Context, key, val interface{}) Context

    這個函數接受一個上下文并返回一個派生的上下文,其中值 val 與 key 相關聯,并與上下文一起經過上下文樹。

    WithValue 方法其實是創建了一個類型為 valueCtx 的上下文,它的類型定義如下:

    type valueCtx struct {
        Context
        key, val interface{}
    }

    這意味著一旦你得到一個帶有值的上下文,任何從它派生的上下文都會得到這個值。該值是不可變的,因此是線程安全的。

    提供的鍵必須是可比較的,并且不應該是字符串類型或任何其他內置類型,以避免使用上下文的包之間發生沖突。 WithValue 的用戶應該為鍵定義自己的類型。

    為避免在分配給 interface{} 時進行分配,上下文鍵通常具有具體類型 struct{}。或者,導出的上下文鍵變量的靜態類型應該是指針或接口。

    package main
    import (
      "context"
      "fmt"
    )
    type contextKey string
    func main() {
      var authToken contextKey = "auth_token"
      ctx := context.WithValue(context.Background(), authToken, "Hello123456")
      fmt.Println(ctx.Value(authToken))
    }

    運行該代碼:

    $ go run .           
    Hello123456

    • func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

    此函數接收父上下文并返回派生上下文,返回 parent 的副本,只是副本中的 Done Channel 是新建的對象,它的類型是 cancelCtx。在這個派生上下文中,添加了一個新的 Done channel,該 channel 在調用 cancel 函數或父上下文的 Done 通道關閉時關閉。

    要記住的一件事是,我們永遠不應該在不同的函數或層之間傳遞這個 cancel ,因為它可能會導致意想不到的結果。創建派生上下文的函數應該只調用取消函數。

    下面是一個使用 Done 通道演示 goroutine 泄漏的示例:

    package main
    import (
      "context"
      "fmt"
      "math/rand"
      "time"
    )
    func main() {
      rand.Seed(time.Now().UnixNano())
      ctx, cancel := context.WithCancel(context.Background())
      defer cancel()
      for char := range randomCharGenerator(ctx) {
        generatedChar := string(char)
        fmt.Printf("%v\n", generatedChar)
        if generatedChar == "o" {
          break
        }
      }
    }
    func randomCharGenerator(ctx context.Context) <-chan int {
      char := make(chan int)
      seedChar := int('a')
      go func() {
        for {
          select {
          case <-ctx.Done():
            fmt.Printf("found %v", seedChar)
            return
          case char <- seedChar:
            seedChar = 'a' + rand.Intn(26)
          }
        }
      }()
      return char
    }

    運行結果:

    $ go run .           
    a
    m
    q
    c
    l
    t
    o

    • func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

    此函數從其父級返回派生上下文,返回一個 parent 的副本。

    當期限超過或調用取消函數時,該派生上下文將被取消。例如,您可以創建一個在未來某個時間自動取消的上下文,并將其傳遞給子函數。當該上下文由于截止日期用完而被取消時,所有獲得該上下文的函數都會收到通知停止工作并返回。如果 parent 的截止日期已經早于 d,則上下文的 Done 通道已經關閉。

    下面是我們正在讀取一個大文件的示例,該文件的截止時間為當前時間 2 毫秒。我們將獲得 2 毫秒的輸出,然后將關閉上下文并退出程序。

    package main
    import (
        "bufio"
        "context"
        "fmt"
        "log"
        "os"
        "time"
    )
    func main() {
        // context with deadline after 2 millisecond
        ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Millisecond))
        defer cancel()
        lineRead := make(chan string)
        var fileName = "sample-file.txt"
        file, err := os.Open(fileName)
        if err != nil {
            log.Fatalf("failed opening file: %s", err)
        }
        scanner := bufio.NewScanner(file)
        scanner.Split(bufio.ScanLines)
        // goroutine to read file line by line and passing to channel to print
        go func() {
            for scanner.Scan() {
                lineRead <- scanner.Text()
            }
            close(lineRead)
            file.Close()
        }()
    outer:
        for {
            // printing file line by line until deadline is reached
            select {
            case <-ctx.Done():
                fmt.Println("process stopped. reason: ", ctx.Err())
                break outer
            case line := <-lineRead:
                fmt.Println(line)
            }
        }
    }
    • func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

    這個函數類似于 context.WithDeadline。不同之處在于它將持續時間作為輸入而不是時間對象。此函數返回一個派生上下文,如果調用取消函數或超過超時持續時間,該上下文將被取消。

    WithTimeout 的實現是:

    func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
        // 當前時間+timeout就是deadline
        return WithDeadline(parent, time.Now().Add(timeout))
    }

    WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))

    package main
    import (
        "bufio"
        "context"
        "fmt"
        "log"
        "os"
        "time"
    )
    func main() {
        // context with deadline after 2 millisecond
        ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
        defer cancel()
        lineRead := make(chan string)
        var fileName = "sample-file.txt"
        file, err := os.Open(fileName)
        if err != nil {
            log.Fatalf("failed opening file: %s", err)
        }
        scanner := bufio.NewScanner(file)
        scanner.Split(bufio.ScanLines)
        // goroutine to read file line by line and passing to channel to print
        go func() {
            for scanner.Scan() {
                lineRead <- scanner.Text()
            }
            close(lineRead)
            file.Close()
        }()
    outer:
        for {
            // printing file line by line until deadline is reached
            select {
            case <-ctx.Done():
                fmt.Println("process stopped. reason: ", ctx.Err())
                break outer
            case line := <-lineRead:
                fmt.Println(line)
            }
        }
    }

    如果父上下文的 Done 通道關閉,它最終將關閉所有派生的 Done 通道(所有后代),如:

    package main
    import (
        "context"
        "fmt"
        "time"
    )
    func main() {
        c := make(chan string)
        go func() {
            time.Sleep(1 * time.Second)
            c <- "one"
        }()
        ctx1 := context.Context(context.Background())
        ctx2, cancel2 := context.WithTimeout(ctx1, 2*time.Second)
        ctx3, cancel3 := context.WithTimeout(ctx2, 10*time.Second) // derives from ctx2
        ctx4, cancel4 := context.WithTimeout(ctx2, 3*time.Second)  // derives from ctx2
        ctx5, cancel5 := context.WithTimeout(ctx4, 5*time.Second)  // derives from ctx4
        cancel2()
        defer cancel3()
        defer cancel4()
        defer cancel5()
        select {
        case <-ctx3.Done():
            fmt.Println("ctx3 closed! error: ", ctx3.Err())
        case <-ctx4.Done():
            fmt.Println("ctx4 closed! error: ", ctx4.Err())
        case <-ctx5.Done():
            fmt.Println("ctx5 closed! error: ", ctx5.Err())
        case msg := <-c:
            fmt.Println("received", msg)
        }
    }

    在這里,由于我們在創建其他派生上下文后立即關閉 ctx2,因此所有其他上下文也會立即關閉,隨機打印 ctx3、ctx4 和 ctx5 關閉消息。 ctx5 是從 ctx4 派生的,由于 ctx2 關閉的級聯效應,它正在關閉。嘗試多次運行,您會看到不同的結果。

    使用 Background 或 TODO 方法創建的上下文沒有取消、值或截止日期。

    package main
    import (
        "context"
        "fmt"
    )
    func main() {
        ctx := context.Background()
        _, ok := ctx.Deadline()
        if !ok {
            fmt.Println("no dealine is set")
        }
        done := ctx.Done()
        if done == nil {
            fmt.Println("channel is nil")
        }
    }

    4.2 Context 使用規范

    • 不要將上下文存儲在結構類型中;相反,將 Context 顯式傳遞給需要它的每個函數。 Context 應該是第一個參數,通常命名為 ctx。

    func DoSomething(ctx context.Context, arg Arg) error {
        // ... use ctx ...
    }
    • 不要傳遞 nil 上下文,即使函數允許。如果不確定要使用哪個 Context,請傳遞 context.TODO 或使用 context.Background() 創建一個空的上下文對象。

    • 僅使用上下文傳遞請求范圍的數據。不要傳遞應該使用函數參數傳遞的數據。

    • 始終尋找 goroutine 泄漏并有效地使用上下文來避免這種情況。

    • 如果父上下文的 Done 通道關閉,它最終將關閉所有派生的 Done 通道(所有后代)

    • 上下文只是臨時做函數之間的上下文傳透,不能持久化上下文

    • key 的類型不應該是字符串類型或者其它內建類型,否則容易在包之間使用 Context 時候產生沖突。使用 WithValue 時,key 的類型應該是自己定義的類型。

    4.3 Context 使用場景

    • 上下文信息傳遞 (request-scoped),比如處理 http 請求、在請求處理鏈路上傳遞信息;

    • 控制子 goroutine 的運行;

    • 超時控制的方法調用;

    • 可以取消的方法調用。

    “Go語言并發編程基礎上下文概念是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    含山县| 沁源县| 秦安县| 全州县| 杭锦后旗| 灵丘县| 西盟| 郁南县| 龙海市| 晋中市| 临夏县| 菏泽市| 隆子县| 和平区| 武平县| 名山县| 阜宁县| 贵港市| 陇川县| 凉山| 南投市| 庆元县| 通州市| 焦作市| 色达县| 沧源| 陕西省| 靖边县| 格尔木市| 泽库县| 永仁县| 安仁县| 嘉定区| 和静县| 莲花县| 舟曲县| 射阳县| 桂平市| 灵丘县| 南陵县| 崇州市|