golang的Context

  • Post author:
  • Post category:golang




golang的Context



  • 介绍

    • 看了李文周老师的博客总结Context的一些概念和用法
    • golang 1.7之后的新库,用来简化对于处理单个请求的多个goroutine之间的请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个API调用
    • 对于服务器内部的函数之间调用必须传递上细纹,或者可以使用WithCancel、WithDeadline、WithTimeout或者WithValue创建派生上下文,当一个上下文被取消时,它派生的所有上下文也被取消。


  • Context接口

    • context.Context是一个接口,改接口定义了四个需要实现的方法,具体签名如下:

    • type Context interface {
          Deadline() (deadline time.Time, ok bool)
          Done() <-chan struct{}
          Err() error
          Value(key interface{}) interface{}
      }
      
      
    • DeadLine 方法返回是截止时间

    • Done返回一个Channel,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用Done方法会返回同一个Channel;

    • Err方法会返回Conetxt结束的原因,它只会在Done返回的Channel被关闭时才返回非空的值;

      • 如果当前的Context被取消就会返回Canceled错误
      • 如果当前Context超时就会返回DeadlineExeeded错误
    • Value方法会从Context中返回键对应的值,对于同一个上下文来说,多次调用Value并传入相同的Key会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据。



  • Background和TODO

    • Go内置两个函数Background和TODO,两个函数分别实现了Context接口的background和todo, 我们代码中最开始以这两个内置的上下文对象作为最顶层partent context,衍生出更多的子上下文对象
    • Background 主要用于main函数、初始化及测试的代码中,作为Context这个树结构的最顶端的Context,也就是根Context
    • TODO 它目前还不知道具体的使用场景,如果我们不知道该使用什么Context的时候,可以使用这个
    • backgroud和todo本质上都是emtyCtx结构体类型,是一个不可取消没有截止时间,没有携带任何值的Context。


  • With系列函数



    • WithCancel
      • WithCancel 返回带有新Done通道的父节点的副本,当调用返回的cancel函数或当关闭父上下问的Done通道时,将关闭返回上下文的Done通道,无论发生什么情况。

      • package study_context
        
        import (
        	"context"
        	"fmt"
        )
        
        func gen(ctx context.Context) <-chan int {
        	dst := make(chan int)
        	n := 1
        	go func() {
        		for {
        			select {
        			case <-ctx.Done():
        				return
        			case dst <- n:
        				n++
        			}
        		}
        	}()
        	return dst
        }
        
        func StartGen() {
        	ctx, cancel := context.WithCancel(context.Background())
        	defer cancel()
        	for n := range gen(ctx) {
        		fmt.Println(n)
        		if n == 5 {
        			break
        		}
        	}
        }
        
        


    • WithDeadline
      • 返回父上下文的副本并将deadline提哦啊正为不迟于d。如果父上下文的deadline已经早于d,则WithDeadline在语义上等同与父上下文。当截止日期时,当调用返回cancel时,或者当副撒谎国内下文的Done通道关闭时,返回上下文的Done通道将被关闭,以最先发生的情况为准。 取消此上下文将释放与之冠来呢的资源因此代码应该在上下文中运行的操作完成后立即调用cancel。

      • package study_context
        
        import (
        	"context"
        	"fmt"
        	"time"
        )
        
        func StudyDeadline() {
        	d := time.Now().Add(10000 * time.Millisecond)
        	ctx, cancel := context.WithDeadline(context.Background(), d)
        	defer cancel()
        	select {
        	case <-time.After(1 * time.Second):
        		fmt.Println("over slept")
        	case <-ctx.Done():
        		fmt.Println(ctx.Err())
        	}
        }
        


    • WithTimeout
      • withTimeout返回WithDeadline(parent, time.Now().Add(timeout))

      • 取消此上下文将释放与相关的资源,因此代码应该在此上下文中运行后立即执行cancel,通常用于数据库或者网络连接的超时操作

      • package study_context
        
        import (
        	"context"
        	"fmt"
        	"sync"
        	"time"
        )
        
        var wg1 sync.WaitGroup
        
        func worker1(ctx context.Context) {
        LOOP:
        	for {
        		time.Sleep(time.Millisecond * 100)
        		select {
        		case <-ctx.Done():
        			fmt.Println("结束")
        			break LOOP
        		default:
        			fmt.Println(".")
        		}
        	}
        	wg1.Done()
        }
        
        func StartTimeout() {
        	ctx, cancel := context.WithTimeout(context.Background(), time.Second*6)
        	wg1.Add(1)
        	go worker1(ctx)
        	time.Sleep(7 * time.Second)
        	fmt.Println("开始执行cancel")
        	cancel()
        	fmt.Println("cancel执行完毕")
        	wg.Wait()
        	fmt.Println("over")
        }
        


    • WithValue
      • WithValue函数能够将请求作用域的数据与context对象建立关系

      • WithValue返回父节点的副本,其中与key关联的值为value

      • 仅对API和进程间传递请求域的数据使用上下文值,而不是使用它来传递可选参数给函数

      • 锁提供的键必须是可比较的,并且不应该是string类型或任何其他内置类型,以避免使用上下文在包之间发生冲突。WithValue的用户应该为键定义自己的类型。为了避免在分配给interface{}时进行分配,上下文键通常具有具体类型struct{}。或者,导出的上下文关键变量的静态类型应该是指针或接口。

      • package study_context
        
        import (
        	"context"
        	"fmt"
        	"sync"
        	"time"
        )
        
        type TraceCode string
        
        var wg2 sync.WaitGroup
        
        func worker2(ctx context.Context) {
        	key := TraceCode("TRACE_CODE")
        	traceCode, ok := ctx.Value(key).(string)
        	if !ok {
        		fmt.Println("invalid trace code")
        	}
        LOOP:
        	for {
        		fmt.Printf("worker, trace, code:%s\n", traceCode)
        		time.Sleep(time.Millisecond * 10)
        		select {
        		case <-ctx.Done():
        			break LOOP
        		default:
        		}
        	}
        	fmt.Println("worker done!")
        	wg2.Done()
        }
        
        func StartValue() {
        	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
        	ctx = context.WithValue(ctx, TraceCode("TRACE_CODE"), "211221221")
        	wg2.Add(1)
        	go worker2(ctx)
        	time.Sleep(time.Second * 5)
        	cancel()
        	wg2.Wait()
        	fmt.Println("over")
        }
        
        


  • 请求超时例子 WithTimeout



    • Client
      • package main
        
        import (
        	"context"
        	"fmt"
        	"io/ioutil"
        	"net/http"
        	"sync"
        	"time"
        )
        
        // respData 是用来记录返回值的结构体
        type respData struct {
        	resp *http.Response
        	err  error
        }
        
        func doCall(ctx context.Context) {
        	transport := http.Transport{
        		DisableKeepAlives: true, // 长连接
        	}
        	client := http.Client{
        		Transport: &transport,
        	}
        	respChan := make(chan *respData, 1) // 创建一个channel来放返回值
        	req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
        	if err != nil {
        		fmt.Printf("new requestg failed, err:%v\n", err)
        		return
        	}
        	req = req.WithContext(ctx) // 将req的context设置为传进来的ctx, 用这个函数给私有属性赋值
        	var wg sync.WaitGroup
        	wg.Add(1)
        	defer wg.Wait()
        	go func() {
        		resp, err := client.Do(req) // 执行req
        		rd := &respData{
        			resp: resp,
        			err:  err,
        		}
        		respChan <- rd
        		wg.Done()
        	}()
        	select {
        	case <-ctx.Done():
        		//transport.CancelRequest(req)
        		fmt.Println("请求超时")
        	case result := <-respChan: // 当请求未超时
        		fmt.Println("请求成功")
        		if result.err != nil {
        			fmt.Printf("请求失败, err:%v\n", result.err)
        			return
        		}
        		defer result.resp.Body.Close()
        		data, _ := ioutil.ReadAll(result.resp.Body)
        		fmt.Printf("请求结果为:%v\n", string(data))
        	}
        }
        
        func main() {
        	ctx, cancle := context.WithTimeout(context.Background(), time.Millisecond*100)
        	defer cancle()
        	doCall(ctx)
        }
        
        


    • Server
      • package main
        
        import (
        	"fmt"
        	"net/http"
        	"time"
        )
        
        func Handeler(w http.ResponseWriter, r *http.Request) {
        	time.Sleep(10 * time.Second)
        	fmt.Fprintf(w, "quick response")
        }
        
        func main() {
        	http.HandleFunc("/", Handeler)
        	err := http.ListenAndServe(":8000", nil)
        	if err != nil {
        		panic(err)
        	}
        }
        



版权声明:本文为weixin_45410801原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。