context
context字面意思是上下文,它有什么用呢?
它主要用于多gorountine、多層級的goroutine(一個goroutine下又有g(shù)oroutine-子goroutine)的情況下,上層對于開啟的gorotine的取消。
另外一個作用是,傳遞值,可以將一些特殊值塞入context中,在后續(xù)可以取出來。
ok,我們簡單總結(jié)下context的作用:
- 控制goroutine的取消
- 值傳遞
下面我們從代碼層面說明,先看最簡單的值傳遞。
1.值傳遞
1.1 context.WithValue值傳遞
這個最簡單,需要注意的是
- 這里的key不能用golang的內(nèi)置類型,需要自定義一個類型;
-
Value()取出之前的存的值
package main
import (
"context"
"fmt"
)
// 上下文值類型 需要自定義類型
type mystring string
func main() {
// mystring("abc") 將“bac"轉(zhuǎn)換成mystring類型
ctx := context.WithValue(context.Background(), mystring("abc"), "hello")
doTask(ctx)
}
func doTask(ctx context.Context) {
// 從上下文中取出值
fmt.Println(ctx.Value(mystring("abc")))
}
// hello
2. 取消
2.1 取消概述
下面我們來看下取消,取消主要有兩種:
- 主動調(diào)用
cancel - 到期取消
timeout/deadline
如何實(shí)現(xiàn)goroutine的取消呢?
是通過Done()方法實(shí)現(xiàn),它是通道;在超時或者手動取消后,會有值彈出;
在后續(xù)goroutine中通過監(jiān)測Done()通道實(shí)現(xiàn)取消。
PS:
- 在context建立后,需要cancel保證資源的釋放
- 在取消后,多次調(diào)用
Done()都會有值返回
好啦!下面看代碼
2.2 手動cancel
package main
import (
"context"
"log"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 保證資源釋放
go doTask(ctx) // goroutine中做任務(wù)
time.Sleep(5 * time.Second) // 5s 后觸發(fā)取消
cancel()
time.Sleep(time.Second) // 等待1s后退出 看到 ctx.Done內(nèi)容
}
func doTask(ctx context.Context) {
for {
select {
case <-ctx.Done(): // 收到取消
log.Printf("取消啦:%s\n", ctx.Err())
return // 退出for循環(huán)
default:
}
time.Sleep(time.Second) //耗時間操作
log.Printf("do task ...\n")
}
}
// 2023/11/18 16:42:04 do task ...
// 2023/11/18 16:42:05 do task ...
// 2023/11/18 16:42:06 do task ...
// 2023/11/18 16:42:07 do task ...
// 2023/11/18 16:42:08 do task ...
// 2023/11/18 16:42:08 取消啦:context canceled
2.3 超時取消
通過context.WithTimeout或者context.Deadline實(shí)現(xiàn)
這兩者使用非常相似,差別在于一個傳遞的是到期時間,另外一個傳時間間隔。
// 傳時間間隔
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
// 傳時間
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
完整代碼如下:
package main
import (
"context"
"log"
"sync"
"time"
)
var w sync.WaitGroup
func main() {
// 5s超時間
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
w.Add(1)
go doTask(ctx) // goroutine中做任務(wù)
w.Wait() // 等待執(zhí)行完
}
func doTask(ctx context.Context) {
for {
select {
case <-ctx.Done(): // 操時收到
log.Printf("取消啦:%s\n", ctx.Err())
w.Done()
return // 退出for循環(huán)
default:
}
time.Sleep(time.Second) //耗時間操作
log.Printf("do task ...\n")
}
}
// 2023/11/18 16:51:40 do task ...
// 2023/11/18 16:51:41 do task ...
// 2023/11/18 16:51:42 do task ...
// 2023/11/18 16:51:43 do task ...
// 2023/11/18 16:51:44 do task ...
// 2023/11/18 16:51:44 取消啦:context deadline exceeded
2.4 多層取消
上面我們演示的都是一層的gorotine的取消,下面看看多層取消(goroutine 又生成goroutine的情況)
package main
import (
"context"
"log"
"time"
)
func main() {
// 5s超時間
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5))
defer cancel()
go doTask(ctx) // goroutine中做任務(wù)
time.Sleep(6 * time.Second) // 留一定時間看task輸出
}
func doTask(ctx context.Context) {
// 子goroutine
go doChildTask(ctx)
for {
select {
case <-ctx.Done(): // 操時收到
log.Printf("do task取消啦: %s\n", ctx.Err())
return // 退出for循環(huán)
default:
}
time.Sleep(time.Second) //耗時間操作
log.Printf("do task ...\n")
}
}
// task的子task
func doChildTask(ctx context.Context) {
for {
select {
case <-ctx.Done(): // 操時收到
log.Printf("child task取消啦: %s\n", ctx.Err())
return // 退出for循環(huán)
default:
}
time.Sleep(time.Second) //耗時間操作
log.Printf("do child task ...\n")
}
}
// 2023/11/18 16:59:34 do child task ...
// 2023/11/18 16:59:34 do task ...
// 2023/11/18 16:59:35 do child task ...
// 2023/11/18 16:59:35 do task ...
// 2023/11/18 16:59:36 do task ...
// 2023/11/18 16:59:36 do child task ...
// 2023/11/18 16:59:37 do child task ...
// 2023/11/18 16:59:37 do task ...
// 2023/11/18 16:59:38 do child task ...
// 2023/11/18 16:59:38 do task ...
// 2023/11/18 16:59:38 child task取消啦: context deadline exceeded
// 2023/11/18 16:59:38 do task取消啦: context deadline exceeded