Golang 退出 goroutine的幾種方式

傳統方式

在剛開始學go的時候,沒用過Context包,那麼退出攜程的方式一般有這麼幾種

使用攜 chan 發送消息通知,這種一般只適合單個goroutine

func exit01() {
	done := make(chan bool)
	go func() {
		for {
			select {
			case <-done:
				fmt.Println("退出攜程")
				return
			default:
				fmt.Println("監控中...")
				time.Sleep(1 * time.Second)
			}
		}
	}()
	time.Sleep(3 * time.Second)
	done <- true
	time.Sleep(5 * time.Second)
	fmt.Println("程序退出")
}

使用關閉 chan 的方式通知多個goroutine退出

func exit02() {
	done :=make(chan bool)
	go func() {
		for{
			select {
			case <-done:
				fmt.Println("退出攜程01")
				return
			default:
				fmt.Println("監控01...")
				time.Sleep(1*time.Second)
			}
		}
	}()

	go func() {
		for res :=range done{ //沒有消息阻塞狀態,chan關閉 for 循環結束
			fmt.Println(res)
		}
		fmt.Println("退出監控03")
	}()

	go func() {
		for{
			select {
			case <-done:
				fmt.Println("退出攜程02")
				return
			default:
				fmt.Println("監控02...")
				time.Sleep(1*time.Second)
			}
		}
	}()
	time.Sleep(3*time.Second)
	close(done)
	time.Sleep(5*time.Second)
	fmt.Println("退出程序")
}

初識 Context包

一個用於手動控制 goroutine 退出或者結束

獲取 context上下文兩種方式

ctx := context.Background() //這隻能用於高等級(在 main 或頂級請求處理中)。這能用於派生我們稍後談及的其他 context

ctx := context.TODO()  // 也只能用於高等級或當您不確定使用什麼 context,或函數以後會更新以便接收一個 context 

他們的底層實現完全一致,不同的是,靜態分析工具可以使用它來驗證 context 是否正確傳遞,
這是一個重要的細節,因爲靜態分析工具可以幫助在早期發現潛在的錯誤,並且可以連接到 CI/CD 管道

var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

使用context.WithTimeout,主動調用 cancel()方法,可以在時間超時之前退出 goroutine

func exit03() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	go func() {
		for{
			select {
			case <-ctx.Done():
				fmt.Println("退出攜程")
				return
			default:
				fmt.Println("請求中..")
				time.Sleep(1*time.Second)
			}
		}
	}()
	time.Sleep(5*time.Second)
	//cancel() //也可以手動調用 cancel()方法退出
	//time.Sleep(2*time.Second)
	fmt.Println("程序退出")

}

使用 context.WithCanel()方法,根據外部條件手動調用 cancel()方法退出
只有創建它的函數才能調用取消函數來取消此 context。如果您願意,可以傳遞取消函數,但是,強烈建議不要這樣做。
這可能導致取消函數的調用者沒有意識到取消 context 的下游影響。可能存在源自此的其他 context,
這可能導致程序以意外的方式運行。簡而言之,永遠不要傳遞取消函數

func exit04() {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		for{
			select {
			case <-ctx.Done():
				fmt.Println("退出攜程")
				return
			default:
				fmt.Println("監控01")
				time.Sleep(1*time.Second)
			}
		}
	}()

	time.Sleep(5*time.Second)
	cancel()
	time.Sleep(2*time.Second)
	fmt.Println("退出程序")

}   

使用 context.WithDeadLine() ,在指定的時間退出 goroutine

func exit05() {
	stringTime := "2019-08-11 09:08:01"
	loc, _ := time.LoadLocation("Local")
	the_time, _ := time.ParseInLocation("2006-01-02 15:04:05", stringTime, loc)
	ctx, _ := context.WithDeadline(context.Background(), the_time)
	go func() {
		for{
			select {
			case <-ctx.Done():
				fmt.Println("退出 goroutine")
				return
			default:
				fmt.Println("監控...")
				time.Sleep(1*time.Second)
			}
		}
	}()

	time.Sleep(60*time.Second)
	fmt.Println("程序退出")

}

使用context.WithValue()傳值,在所有的context樹中都能獲取到該值,如果設置相同的key 則覆蓋該值
不建議使用 context 值傳遞關鍵參數,而是函數應接收簽名中的那些值,使其顯式化。

func exit06() {
	ctx := context.WithValue(context.Background(), "msg", "hello word")
	go func(ctx context.Context) {
		fmt.Println(ctx.Value("msg"))
	}(ctx)
	time.Sleep(2*time.Second)
	fmt.Println("程序退出")
}

相關建議約束

  • context.Background 只應用在最高等級,作爲所有派生 context 的根。
  • context.TODO 應用在不確定要使用什麼的地方,或者當前函數以後會更新以便使用 context。
  • context 取消是建議性的,這些函數可能需要一些時間來清理和退出。
  • context.Value 應該很少使用,它不應該被用來傳遞可選參數。這使得 API 隱式的並且可以引起錯誤。取而代之的是,這些值應該作爲參數傳遞。
  • 不要將 context 存儲在結構中,在函數中顯式傳遞它們,最好是作爲第一個參數。
  • 永遠不要傳遞不存在的 context 。相反,如果您不確定使用什麼,使用一個 ToDo context。
  • Context 結構沒有取消方法,因爲只有派生 context 的函數才應該取消 context。

time包中的Ticker,Timer

ticker是每隔一段時間就會觸發依次

timer是定時器,只會觸發一次

func test01() {

//每隔一段時間觸發一次
ticker := time.NewTicker(time.Second * 1)
go func() {
	for{
		<-ticker.C
		fmt.Println("ticker")
	}
}()

//只會觸發一次
timer := time.NewTimer(time.Second * 2)
go func() {
	for{
		<-timer.C
		fmt.Println("timer")
	}
}()
time.Sleep(time.Second * 20)
}

time.NewTimer和Reset()函數實現定時觸發,Reset()函數可能失敗,經測試。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章