- 1.Context接口
- 2.emptyCtx
- 3.Deadline()方法
- 4.Done()方法
- 5.Err方法
- 6.Value方法()
- 7.contex應用場景
- 8.其他context方法
1.Context接口
Context接口只有四個方法,以下是context源碼。
type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key any) any
}
2.emptyCtx
context接口源碼中有兩個對外的實現,context.Background()和context.TODO(),都返回一個emptyCtx。
Background()和TODO()可以看作是emptyCtx的別名,用法如下:
- Background(),當我們自己創建一個context,可以用context.Background();
- TODO(),當我們調用一個方法,方法有個參數是context,我們又沒有context可以傳,就可以傳context.TODO()。
func Background() Context {return backgroundCtx{}
}
func TODO() Context {return todoCtx{}
}
type backgroundCtx struct{ emptyCtx }
type todoCtx struct{ emptyCtx }type emptyCtx struct{}
func (emptyCtx) Deadline() (deadline time.Time, ok bool) {return
}
func (emptyCtx) Done() <-chan struct{} {return nil
}
func (emptyCtx) Err() error {return nil
}
func (emptyCtx) Value(key any) any {return nil
}
3.Deadline()方法
Deadline() (deadline time.Time, ok bool)
返回這個context的deadline(結束時間)和ok,如果context設置了deadline,ok=ture,反之ok=false
如下可以看到,context如果沒有設置deadline,則默認時間是“0001-01-01 00:00:00 +0000 UTC”
func TestContextDeadline(t *testing.T) {ctx := context.Background()deadline, ok := ctx.Deadline()fmt.Println(deadline) //輸出,0001-01-01 00:00:00 +0000 UTCfmt.Print(ok) // 輸出,false
}
下面這是設置了時間的,可以看到dealine是當前時間加10秒,
func TestContextDeadline(t *testing.T) {fmt.Println(time.Now())ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))deadline, ok := ctx.Deadline()fmt.Println(deadline)fmt.Println(ok)
}
輸出:
2025-03-09 15:58:20.2027155 +0800 CST m=+0.001014101
2025-03-09 15:58:30.2080898 +0800 CST m=+10.006388401
true
如里對一個子contex設置的deadline時間比已有的contex大(就是比父context大),則不會生效
func TestContextDeadline2(t *testing.T) {fmt.Println(time.Now())ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))fmt.Println(ctx.Deadline())ctx2, _ := context.WithDeadline(ctx, time.Now().Add(15*time.Second))fmt.Println(ctx2.Deadline())
}
輸出:
2025-03-09 18:11:08.6194041 +0800 CST m=+0.001034701
2025-03-09 18:11:18.6251276 +0800 CST m=+10.006758201 true
2025-03-09 18:11:18.6251276 +0800 CST m=+10.006758201 true
ctx2的dealine并沒有加15秒,而是和父deadline一樣。
如果子deadline比父小,子deadline就會生效,并且父deadline不受影響。如下:
func TestContextDeadline2(t *testing.T) {fmt.Println(time.Now())ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))fmt.Println(ctx.Deadline())ctx2, _ := context.WithDeadline(ctx, time.Now().Add(5*time.Second))fmt.Println(ctx2.Deadline())fmt.Println(ctx.Deadline())
}
輸出:
2025-03-09 18:16:08.5541102 +0800 CST m=+0.003605801
2025-03-09 18:16:18.5612081 +0800 CST m=+10.010703701 true
2025-03-09 18:16:13.5612081 +0800 CST m=+5.010703701 true
2025-03-09 18:16:18.5612081 +0800 CST m=+10.010703701 true
4.Done()方法
Done() <-chan struct{}
Done()返回一個chan,當調用<-ctx.Done(),會一直阻塞,如果ctx的deadline時間到了,才能從chan返回
func TestContextDone(t *testing.T) {fmt.Println(time.Now())ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))deadline, ok := ctx.Deadline()fmt.Println(deadline)fmt.Println(ok)<-ctx.Done() //Done()會一直阻塞等到deadline時間到了才結束fmt.Println(time.Now()) //當前時間加了10秒,因為<-ctx.Done() 阻塞了10秒
}
輸出:
2025-03-09 16:02:27.898926 +0800 CST m=+0.001548101
2025-03-09 16:02:37.9048716 +0800 CST m=+10.007493701
true
2025-03-09 16:02:37.9052627 +0800 CST m=+10.007884801
注意context.WithDeadline()方法還返回一個cancel
func TestContextDeadline(t *testing.T) {fmt.Println(time.Now())ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))deadline, ok := ctx.Deadline()fmt.Println(deadline)fmt.Println(ok)cancel() //cancel會讓context立刻結束,<-ctx.Done() ///Done()不會阻塞fmt.Println(time.Now())
}
輸出:
2025-03-09 17:25:36.815137 +0800 CST m=+0.001292801
2025-03-09 17:25:46.8209512 +0800 CST m=+10.007107001
true
2025-03-09 17:25:36.8209512 +0800 CST m=+0.007107001
如果ctx沒有設置deadline,ctx.Done()返回nil
func TestContextDone(t *testing.T) {ctx := context.Background()fmt.Println(ctx.Done()) //輸出nil
}
5.Err方法
Err() error
返回一個錯誤,有兩種錯誤
1.deadline時間到了
2.ctx被cancel了
以下是deadline時間到了的示例:
- ctx設置10秒后結束 ,第一次調用ctx.Err(),輸出nill(因為還沒到10秒,ctx還沒結束)
- 等到11秒后,ctx.Err()輸出了結束原因context deadline exceeded(deadline到時間結束)。
如果ctx結束了,調用ctx.Err()返回的結果一樣,下面調用了三次,結果一樣。
func TestContextErr(t *testing.T) {fmt.Println(time.Now())ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))fmt.Println(ctx.Err())time.Sleep(11 * time.Second)fmt.Println(time.Now())fmt.Println(ctx.Err())fmt.Println(ctx.Err())fmt.Println(ctx.Err())
}
輸出:
2025-03-09 16:17:04.7489444 +0800 CST m=+0.001009401
<nil>
2025-03-09 16:17:15.7551472 +0800 CST m=+11.007212201
context deadline exceeded
context deadline exceeded
context deadline exceeded
以下是cancel示例
func TestContextDeadline(t *testing.T) {fmt.Println(time.Now())ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))deadline, ok := ctx.Deadline()fmt.Println(deadline)fmt.Println(ok)cancel() //調用了cancel()<-ctx.Done() fmt.Println(time.Now())fmt.Println(ctx.Err())
}
輸出:
2025-03-09 17:54:11.8989047 +0800 CST m=+0.001138201
2025-03-09 17:54:21.9048333 +0800 CST m=+10.007066801
true
2025-03-09 17:54:11.9048333 +0800 CST m=+0.007066801
context canceled
6.Value方法()
Value(key any) any
就是往context存了key/value形式的數據,然后通過Value()方法取出這個值。
func TestContextValue(t *testing.T) {ctx := context.WithValue(context.Background(), "name", "daniel")fmt.Println(ctx.Value("name")) //輸出daniel
}
7.contex應用場景
context是多線程安全的,常用于并發控制技術,在不同的goroutine之間同步請求特定的數據、取消信號以及處理請求的dealine(截止日期)。
通知其他goroutine結束
如下所示,設置findUser()方法只能查找用戶10秒鐘,10秒后強制結束這個goroutine。
func findUser(ctx context.Context, id int) {//輸出ctx結束時間fmt.Println(ctx.Deadline())for {//模擬根據id查找用戶time.Sleep(2 * time.Second)select {case <-ctx.Done():fmt.Println("ctx被取消,查找結束")fmt.Println(ctx.Err())fmt.Println(time.Now())returndefault:fmt.Println("正在查找中...")}}
}func TestContext(t *testing.T) {fmt.Println(time.Now())ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))go findUser(ctx, 100)time.Sleep(20 * time.Second)
}
輸出:
2025-03-09 16:46:05.7818671 +0800 CST m=+0.000000001
2025-03-09 16:46:15.7892368 +0800 CST m=+10.007369701 true
正在查找中...
正在查找中...
正在查找中...
正在查找中...
ctx被取消,查找結束
context deadline exceeded
2025-03-09 16:46:15.7919931 +0800 CST m=+10.010126001
8.其他context方法
cotext還有很多其他方法,相當于返回context的一個特定實現(context內部的實現),各有不同的功能。
ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
比如上面這個會返回一個帶deadline方法,其實是返回一個timerCtx,里面記下了deadline,
可以看到這些方法里面都有c.m.lock()方法,所以context是多線程安全的。
context源碼也比較簡單,有興趣自行看看源碼。
timerCtx開頭是小寫的,也就是context內部的實現
context.WithDeadlineCause()也可以自定義一個cause
func TestContextDeadlineCause(t *testing.T) {ctx, _ := context.WithDeadlineCause(context.Background(),time.Now().Add(10*time.Second), errors.New("my error"))fmt.Println(context.Cause(ctx))time.Sleep(11 * time.Second) //context.Cause(ctx)先輸出nill,等結事時間到了才輸出真正的causefmt.Println(context.Cause(ctx))
}
輸出
<nil>
my error