iT邦幫忙

2022 iThome 鐵人賽

DAY 18
0
Software Development

你知道Go是什麼嗎?系列 第 18

Day18 - Context - Golang

  • 分享至 

  • xImage
  •  

好的,今天來介紹Context套件吧!之前在提goroutine時有用過兩種方式來終結goroutine,一種是設置sync.WaitGroup,另一種是用channel控制,而Context又是另一種在各個goroutine間傳送訊號的方式,通常用來送出取消或結束的訊號,是Go在1.7版本時放入標準函式庫的套件。

Context

官方文件在這邊

使用規範

官方文件中列出了幾件事情要注意的

  • 不要將Context存在struct
  • Context建立出來,並當作參數傳遞給需要使用到的函式
  • Context放在第一個參數,並命名ctx
  • 別宣告Context並傳入nil,若還不清楚要用於什麼情況可使用TODO方式宣告

使用目的

更靈活的操作各種goroutine,可主動結束或是設置結束條件,以http請求來說,若請求超時的話就必須重啟或斷掉請求,或是由使用者送出結束請求時要將進行中的goroutine關掉,Context很適合運用在這些地方。

建立Context

套件提供兩種方法宣告Context

  • context.Background(): 返回一個非nilContext,通常用於最上層的傳入,像是maininittests
  • context.TODO():返回一個非nilContext,當還未清楚要如何使用或尚不可用時可以使用TODO方式宣告。

反正大多時候是使用第一種方式建立Context,我學藝不清還沒遇到要使用下面宣告的狀況。

實做Context

在建立出父類別Context後,可以透過四個With函式使用不同的Context並應對各種場景。

// 設置cancle,可主動關閉所有相關的goroutine
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// 設置結束時間,在結束時間到時結束相關goroutine
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

// 設置超時偵測,在超過預期時間時結束相關goroutine
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

// 設置共通變數,可在相關goroutine中都使用此變數
func WithValue(parent Context, key, val interface{}) Context

WithCancel

使用方式如下,宣告最上層Context後傳入With函式,並將ctx傳入需要的函式。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())
	
	go repeat(ctx) // 執行無窮迴圈程式

    time.Sleep(3 * time.Second)
    fmt.Println("使用cancel()")
    cancel()
    time.Sleep(1 * time.Second)
}

func repeat(ctx context.Context){
	for {
		select {
		case <-ctx.Done():
			fmt.Println("閉嘴啦吵死了")
			return
		default:
			fmt.Println("兔子兔子兔子兔子兔子")
			time.Sleep(1 * time.Second)
		}
	}
}

output:

兔子兔子兔子兔子兔子
兔子兔子兔子兔子兔子
兔子兔子兔子兔子兔子
使用cancel()
閉嘴啦吵死了

WithDeadline / WithTimeout

兩個使用方式差不多,我這邊試寫Timeout就好,輸出可以看到執行三秒後程式就自己終止了。

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
	borrowRabbit(ctx)
	time.Sleep(time.Second)
}

func borrowRabbit(ctx context.Context){
	for {
		select {
		case <-ctx.Done():
			fmt.Println("兔子回汐止了")
			return
		default:
			fmt.Println("阿現在兔兔在我這")
			time.Sleep(1 * time.Second)
		}
	}
}

output:
阿現在兔兔在我這
阿現在兔兔在我這
阿現在兔兔在我這
兔子回汐止了

WithValue

這個Context不支援CancelFuncWithValue的功用是夾帶遷入的value並傳遞在各個子Context間。

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	ctx := context.WithValue(context.Background(), "price", "17 million")
	Get(ctx, "price")
	go func() {
		// 這邊將父Context傳入建立ctx2
		ctx2 := context.WithValue(ctx, "name", "SM兔兔")

		Get(ctx2, "price") // 從ctx2讀取父Context的資料
		go func() {
			Get(ctx2, "name")
		}()
	}()

	time.Sleep(1 * time.Second)

}

// 取得對應key值之value
func Get(ctx context.Context, k string) {
	if v, ok := ctx.Value(k).(string); ok {
		fmt.Println(k, v)
	}
}

Context的部分差不多介紹到這,除了內建的四種使用方法也可以自定義Context,算是個滿方便的功能,熟練使用的話也能更好的管理goroutine

參考資料

Document
https://pkg.go.dev/context

【Go语言】小白也能看懂的context包详解:从入门到精通
https://segmentfault.com/a/1190000040917752

[pkg] context
https://pjchender.dev/golang/pkg-context/

Go Context 101
https://medium.com/codex/go-context-101-ebfaf655fa95


上一篇
Day17 - Gin - API Server - Golang
下一篇
Day19 - Defer、Panic、Recover - Golang
系列文
你知道Go是什麼嗎?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言