context簡單概述:
Go服務器的每個請求都有自己的goroutine,而有的請求為了提高性能,會經常啟動額外的goroutine處理請求,當該請求被取消或超時,該請求上的所有goroutines應該退出,防止資源泄露。那么context來了,它對該請求上的所有goroutines進行約束,然后進行取消信號,超時等操作。
而context優點就是簡潔的管理goroutines的生命周期。
context簡單使用:
接下來模擬一個超時繼續分析context
注意: 使用時遵循context規則
1. 不要將 Context放入結構體,Context應該作為第一個參數傳
入,命名為ctx。
2. 即使函數允許,也不要傳入nil的 Context。如果不知道用哪種
Context,可以使用context.TODO()。
3. 使用context的Value相關方法,只應該用于在程序和接口中傳遞
和請求相關數據,不能用它來傳遞一些可選的參數
4. 相同的 Context 可以傳遞給在不同的goroutine;Context 是
并發安全的。
使用net/http/pprof對goroutines進行查看:
package main
import (
"context"
"fmt"
"net/http"
_ "net/http/pprof"
"time"
)
func main() {
go http.ListenAndServe(":8080", nil)
ctx, _ := context.WithTimeout(context.Background(), (10 * time.Second))
go testA(ctx)
select {}
}
func testA(ctx context.Context) {
ctxA, _ := context.WithTimeout(ctx, (5 * time.Second))
ch := make(chan int)
go testB(ctxA, ch)
select {
case <-ctx.Done():
fmt.Println("testA Done")
return
case i := <-ch:
fmt.Println(i)
}
}
func testB(ctx context.Context, ch chan int) {
//模擬讀取數據
sumCh := make(chan int)
go func(sumCh chan int) {
sum := 10
time.Sleep(10 * time.Second)
sumCh <- sum
}(sumCh)
select {
case <-ctx.Done():
fmt.Println("testB Done")
<-sumCh
return
//case ch <- <-sumCh: 注意這樣會導致資源泄露
case i := <-sumCh:
fmt.Println("send", i)
ch <- i
}
}
從執行中和執行后的結果來看,我們完美的關閉了不需要的goroutine,
概述中提到:
當應用場景是由一個請求衍生出多個goroutine完成需求,那它們之間就需要滿足一定的約束關系,才能中止routine樹,超時等操作。
那么是如何到達約束關系?
簡單理解,Context 的調用以鏈式存在,通過WithXxx方法派生出新的 Context與當前父Context 關聯,當父 Context 被取消時,其派生的所有 Context 都將取消。
上圖WithCancel派生簡單分析圖,接下里我們進行源碼分析,進一步了解Context的約束關系(WithXxx方法派生大概與WithCancel相同)。
Context源碼分析:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline() 返回的time.Time 它為Context 的結束的時間,ok 表示是否有 deadline
Done() 返回一個信道,當對Context進行撤銷或過期時,該信道就會關閉的,可以簡單認為它是關閉信號。
Err() 當Done信道關閉后,Err會返回關閉的原因(如超時,手動關閉)
Value(key interface{}) 一個 K-V 存儲的方法
canceler提供了cancal函數,同時要求數據結構實現Context
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
//默認錯誤
var Canceled = errors.New("context canceled")
var DeadlineExceeded = errors.New("context deadline exceeded")
實現Context的數據結構
type emptyCtx int
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 interface{}) interface{} {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
兩個實現Context的空結構。
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
cancelCtx結構體繼承了Context,同時實現了canceler接口:
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // 當被cancel時將會把err設置為非nil
}
func (c *cancelCtx) Done() <-chan struct{} {
return c.done
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
}
func (c *cancelCtx) String() string {
return fmt.Sprintf("%v.WithCancel", c.Context)
}
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
//timerCtx會頻繁使用這塊代碼,因為派生出來
//timerCtx全部指向同一個cancelCtx.
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
//關閉c.done
close(c.done)
//依次遍歷c.children,每個child分別cancel
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
//如果removeFromParent為true,則將c從其parent的children中刪除
if removeFromParent {
removeChild(c.Context, c)
}
}
//如果parent為valueCtx類型,將循環找最近parent
//為CancelCtx類型的,找到就從父對象的children
//map 中刪除這個child,否則返回nil(context.Background或者 context.TODO)
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
接下來分析Cancel相關代碼
type CancelFunc func()
//WithCancel方法返回一個繼承parent的Context對
//象,同時返回的cancel方法可以用來關閉當前
//Context中的Done channel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
}
func propagateCancel(parent Context, child canceler) {
if parent.Done() == nil {
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
//如果找到的parent已經被cancel,
//則將方才傳入的child樹給cancel掉
child.cancel(false, p.err)
} else {
//否則, 將child節點直接添加到parent的children中
//(這樣就有了約束關系,向上的父親指針不變
//,向下的孩子指針可以直接使用 )
if p.children == nil {
p.children = make(map[canceler]bool)
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
//如果沒有找到最近的可以被cancel的parent,
//則啟動一個goroutine,等待傳入的parent終止,
//并cancel傳入的child樹,或者等待傳入的child終結。
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
//判斷parent是否為cancelCtx類型
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true
case *timerCtx:
return &c.cancelCtx, true
case *valueCtx:
parent = c.Context
default:
return nil, false
}
}
}
timerCtx 繼承cancelCtx的結構體,這種設計就可以避免寫重復代碼,提高復用
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu. 計時器
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) String() string {
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
}
// 與cencelCtx有所不同,除了處理cancelCtx.cancel,
// 還回對c.timer進行Stop(),并將c.timer=nil
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
timerCtx具體的兩個方法:
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
//如果parent的deadline比新傳入的deadline早,則直接
//返回WithCancel,因為parent的deadline會先失效,而新的
//deadline根據不需要
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: deadline,
}
propagateCancel(parent, c)
//檢查如果已經過期,則cancel新child樹
d := deadline.Sub(time.Now())
if d <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
//沒有被cancel的話,就設置deadline之后cancel的計時器
c.timer = time.AfterFunc(d, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
// WithTimeout簡單暴力,直接把WithTimeout返回
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
valueCtx主要用來傳遞一些可比較操作的數據
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflect.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) String() string {
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
總結完了,如果哪里不足,請大家提出問題一起學習,謝謝!