傳統方式
在剛開始學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()函數可能失敗,經測試。