golang通過(guò)context控制并發(fā)的應(yīng)用場(chǎng)景實(shí)現(xiàn)
golang 里出現(xiàn)多 goroutine 的場(chǎng)景很常見(jiàn), 最常用的兩種方式就是 WaitGroup 和 Context, 今天我們了解一下 Context 的應(yīng)用場(chǎng)景
使用場(chǎng)景
場(chǎng)景一: 多goroutine執(zhí)行超時(shí)通知
并發(fā)執(zhí)行的業(yè)務(wù)中最常見(jiàn)的就是有協(xié)程執(zhí)行超時(shí), 如果不做超時(shí)處理就會(huì)出現(xiàn)一個(gè)僵尸進(jìn)程, 這累計(jì)的多了就會(huì)有一陣手忙腳亂了, 所以我們要在源頭上就避免它們
看下面這個(gè)示例:
package main import ( "context" "fmt" "time" ) /** 同一個(gè)content可以控制多個(gè)goroutine, 確保線程可控, 而不是每新建一個(gè)goroutine就要有一個(gè)chan去通知他關(guān)閉 有了他代碼更加簡(jiǎn)潔 */ 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 秒過(guò)去了 \n", index) time.Sleep(1 * time.Second) if index > 10 { break } } fmt.Println("通知停止監(jiān)控") // 其實(shí)此時(shí)已經(jīng)超時(shí), 協(xié)程已經(jīng)提前退出 cancel() // 防止主進(jìn)程提前退出 time.Sleep(3 * time.Second) fmt.Println("done") } func watch(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Printf("%s 監(jiān)控退出, 停止了...\n", name) return default: fmt.Printf("%s goroutine監(jiān)控中... \n", name) time.Sleep(2 * time.Second) } } }
使用 context.WithTimeout() 給文本流設(shè)置一個(gè)時(shí)間上限, 結(jié)合 for+select 去接收消息. 當(dāng)執(zhí)行超時(shí),或手動(dòng)關(guān)閉都會(huì)給 <-ctx.Done() 發(fā)送消息,而且所有使用同一個(gè) context 都會(huì)收到這個(gè)通知, 免去了一個(gè)一個(gè)通知的繁瑣代碼
場(chǎng)景二: 類似web服務(wù)器中的session
比如在php中(沒(méi)用swoole擴(kuò)展), 一個(gè)請(qǐng)求進(jìn)來(lái), 從 $_REQUEST $_SERVER 能獲取到的是有關(guān)這一條請(qǐng)求的所有信息, 哪怕是使用全局變量也是給這一個(gè)請(qǐng)求來(lái)服務(wù)的, 是線程安全的
但是 golang 就不一樣了, 因?yàn)槌绦虮旧砭湍芷鹨粋€(gè) web sever, 因此就不能隨便使用全局變量了, 不然就是內(nèi)存泄露警告. 但是實(shí)際業(yè)務(wù)當(dāng)中需要有一個(gè)類似session 的東西來(lái)承載單次請(qǐng)求的信息, 舉一個(gè)具體的例子就是: 給每次請(qǐng)求加一個(gè) uniqueID 該如何處理? 有了這個(gè) uniqueID, 請(qǐng)求的所有日志都能帶上它, 這樣排查問(wèn)題的時(shí)候方便追蹤一次請(qǐng)求發(fā)生了什么
如下:
func demo2() { pCtx, pCancel := context.WithCancel(context.Background()) pCtx = context.WithValue(pCtx, "parentKey", "parentVale") go watch(pCtx, "[父進(jìn)程1]") go watch(pCtx, "[父進(jìn)程2]") cCtx, cCancel := context.WithCancel(pCtx) go watch(cCtx, "[子進(jìn)程1]") go watch(cCtx, "[子進(jìn)程2]") fmt.Println(pCtx.Value("parentKey")) fmt.Println(cCtx.Value("parentKey")) time.Sleep(10 * time.Second) fmt.Println("子進(jìn)程關(guān)閉") cCancel() time.Sleep(5 * time.Second) fmt.Println("父進(jìn)程關(guān)閉") pCancel() time.Sleep(3 * time.Second) fmt.Println("done") }
最開(kāi)始的 context.WithCancel(context.Background()) 中 context.Background() 就是一個(gè)新建的 context, 利用 context 能繼承的特性, 可以將自己的程序構(gòu)建出一個(gè) context 樹, context 執(zhí)行 cancel() 將影響到當(dāng)前 context 和子 context, 不會(huì)影響到父級(jí).
同時(shí) context.WithValue 也會(huì)給 context 帶上自定義的值, 這樣 uniqueID 就能輕松的傳遞了下去, 而不是一層層的傳遞參數(shù), 改func什么的
對(duì)于 context 很值得參考的應(yīng)用有:
Context 相關(guān) func 和接口
繼承 context 需要實(shí)現(xiàn)如下四個(gè)接口
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
當(dāng)使用的時(shí)候不需要實(shí)現(xiàn)接口, 因?yàn)楣俜桨镆呀?jīng)基于 emptyCtx 實(shí)現(xiàn)了一個(gè), 調(diào)用方法有
var ( background = new(emptyCtx) todo = new(emptyCtx) ) // 這個(gè)是最初始的ctx, 之后的子ctx都是繼承自它 func Background() Context { return background } // 不清楚context要干嘛, 但是就得有一個(gè)ctx的用這個(gè) func TODO() Context { return todo }
繼承用的函數(shù)
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
- WithCancel 返回一個(gè)帶 cancel 函數(shù)的ctx,
- WithDeadline 在到達(dá)指定時(shí)間時(shí)自動(dòng)執(zhí)行 cancel()
- WithTimeout 是 WithDeadline的殼子, 區(qū)別就是這個(gè)函數(shù)是多少時(shí)間過(guò)后執(zhí)行 cancel
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
WithValue 繼承父類ctx時(shí)順便帶上一個(gè)值
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Go用兩個(gè)協(xié)程交替打印100以內(nèi)的奇偶數(shù)的方法詳解
這篇文章主要給大家詳細(xì)介紹了Go用兩個(gè)協(xié)程交替打印100以內(nèi)的奇偶數(shù)的示例代碼,文中給大家介紹了兩個(gè)實(shí)現(xiàn)方法,使用無(wú)緩沖的channel和設(shè)置GOMAXPROCS=1,介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08golang并發(fā)執(zhí)行的幾種方式小結(jié)
本文主要介紹了golang并發(fā)執(zhí)行的幾種方式小結(jié),主要包括了Channel,WaitGroup ,Context,使用這三種機(jī)制中的一種或者多種可以達(dá)到并發(fā)控制很好的效果,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08幾個(gè)小技巧幫你實(shí)現(xiàn)Golang永久阻塞
Go 的運(yùn)行時(shí)的當(dāng)前設(shè)計(jì),假定程序員自己負(fù)責(zé)檢測(cè)何時(shí)終止一個(gè) goroutine 以及何時(shí)終止該程序。有時(shí)候我們需要的是使程序阻塞在這一行,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2021-12-12golang 實(shí)現(xiàn)tcp轉(zhuǎn)發(fā)代理的方法
今天小編就為大家分享一篇golang 實(shí)現(xiàn)tcp轉(zhuǎn)發(fā)代理的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08Go?io/fs.FileMode文件系統(tǒng)基本操作和權(quán)限管理深入理解
這篇文章主要為大家介紹了Go?io/fs.FileMode文件系統(tǒng)基本操作和權(quán)限管理深入理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01go開(kāi)發(fā)中引用靜態(tài)庫(kù).a文件的方法
這篇文章主要介紹了go開(kāi)發(fā)中引用靜態(tài)庫(kù).a文件的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11golang并發(fā)編程中Goroutine 協(xié)程的實(shí)現(xiàn)
Go語(yǔ)言中的協(xié)程是一種輕量級(jí)線程,通過(guò)在函數(shù)前加go關(guān)鍵字來(lái)并發(fā)執(zhí)行,具有動(dòng)態(tài)棧、快速啟動(dòng)和低內(nèi)存使用等特點(diǎn),本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2024-10-10