您好,登錄后才能下訂單哦!
golang 里出現多 goroutine 的場景很常見, 最常用的兩種方式就是 WaitGroup 和 Context, 今天我們了解一下 Context 的應用場景
使用場景
場景一: 多goroutine執行超時通知
并發執行的業務中最常見的就是有協程執行超時, 如果不做超時處理就會出現一個僵尸進程, 這累計的多了就會有一陣手忙腳亂了, 所以我們要在源頭上就避免它們
看下面這個示例:
package main import ( "context" "fmt" "time" ) /** 同一個content可以控制多個goroutine, 確保線程可控, 而不是每新建一個goroutine就要有一個chan去通知他關閉 有了他代碼更加簡潔 */ func main() { fmt.Println("run demo \n\n\n") demo() } func demo() { ctx, cancel := context.WithTimeout(context.Background(), 9*time.Second) go watch(ctx, "[線程1]") go watch(ctx, "[線程2]") go watch(ctx, "[線程3]") index := 0 for { index++ fmt.Printf("%d 秒過去了 \n", index) time.Sleep(1 * time.Second) if index > 10 { break } } fmt.Println("通知停止監控") // 其實此時已經超時, 協程已經提前退出 cancel() // 防止主進程提前退出 time.Sleep(3 * time.Second) fmt.Println("done") } func watch(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Printf("%s 監控退出, 停止了...\n", name) return default: fmt.Printf("%s goroutine監控中... \n", name) time.Sleep(2 * time.Second) } } }
使用 context.WithTimeout() 給文本流設置一個時間上限, 結合 for+select 去接收消息. 當執行超時,或手動關閉都會給 <-ctx.Done() 發送消息,而且所有使用同一個 context 都會收到這個通知, 免去了一個一個通知的繁瑣代碼
場景二: 類似web服務器中的session
比如在php中(沒用swoole擴展), 一個請求進來, 從 $_REQUEST $_SERVER 能獲取到的是有關這一條請求的所有信息, 哪怕是使用全局變量也是給這一個請求來服務的, 是線程安全的
但是 golang 就不一樣了, 因為程序本身就能起一個 web sever, 因此就不能隨便使用全局變量了, 不然就是內存泄露警告. 但是實際業務當中需要有一個類似session 的東西來承載單次請求的信息, 舉一個具體的例子就是: 給每次請求加一個 uniqueID 該如何處理? 有了這個 uniqueID, 請求的所有日志都能帶上它, 這樣排查問題的時候方便追蹤一次請求發生了什么
如下:
func demo2() { pCtx, pCancel := context.WithCancel(context.Background()) pCtx = context.WithValue(pCtx, "parentKey", "parentVale") go watch(pCtx, "[父進程1]") go watch(pCtx, "[父進程2]") cCtx, cCancel := context.WithCancel(pCtx) go watch(cCtx, "[子進程1]") go watch(cCtx, "[子進程2]") fmt.Println(pCtx.Value("parentKey")) fmt.Println(cCtx.Value("parentKey")) time.Sleep(10 * time.Second) fmt.Println("子進程關閉") cCancel() time.Sleep(5 * time.Second) fmt.Println("父進程關閉") pCancel() time.Sleep(3 * time.Second) fmt.Println("done") }
最開始的 context.WithCancel(context.Background()) 中 context.Background() 就是一個新建的 context, 利用 context 能繼承的特性, 可以將自己的程序構建出一個 context 樹, context 執行 cancel() 將影響到當前 context 和子 context, 不會影響到父級.
同時 context.WithValue 也會給 context 帶上自定義的值, 這樣 uniqueID 就能輕松的傳遞了下去, 而不是一層層的傳遞參數, 改func什么的
對于 context 很值得參考的應用有:
Context 相關 func 和接口
繼承 context 需要實現如下四個接口
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
當使用的時候不需要實現接口, 因為官方包里已經基于 emptyCtx 實現了一個, 調用方法有
var ( background = new(emptyCtx) todo = new(emptyCtx) ) // 這個是最初始的ctx, 之后的子ctx都是繼承自它 func Background() Context { return background } // 不清楚context要干嘛, 但是就得有一個ctx的用這個 func TODO() Context { return todo }
繼承用的函數
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
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
WithValue 繼承父類ctx時順便帶上一個值
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。