开发常用

context 包

Go 语言中的每一个请求的都是通过一个单独的 Goroutine 进行处理的,HTTP/RPC 请求的处理器往往都会启动新的 Goroutine 访问数据库和 RPC 服务,我们可能会创建多个 Goroutine 来处理一次请求,而 Context 的主要作用就是在不同的 Goroutine 之间同步请求特定的数据、取消信号以及处理请求的截止日期。

Context结构

// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
    // Done returns a channel that is closed when this Context is canceled
    // or times out.
    Done() <-chan struct{}

    // Err indicates why this context was canceled, after the Done channel
    // is closed.
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}
}
  • Deadline 方法需要返回当前 Context 被取消的时间,也就是完成工作的截止日期;

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

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

    • 如果当前 Context 被取消就会返回 Canceled 错误;

    • 如果当前 Context 超时就会返回 DeadlineExceeded 错误;

  • Value 方法会从 Context 中返回键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,这个功能可以用来传递请求特定的数据;

Context 的实现方法

根context

Context 虽然是个接口,但是并不需要使用方实现:Background() TODO

var (
	background = new(emptyCtx)
	todo = new(emptyCtx)
)
func Background() Context { 
  //主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context,它不能被取消
	return background
}
func TODO() Context { //只在不确定时使用 context.TODO()
	return todo
}

一般在代码中,开始上下文的时候都是以这两个作为最顶层的parent context,然后再衍生出子context。这些 Context 对象形成一棵树:当一个 Context 对象被取消时,继承自它的所有Context都会被取消。

继承context

一般情况下是以context.Background() 做为根节点

//传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

//会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,也可以提前通过取消函数进行取消。
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

//超时自动取消,是多少时间后自动取消Context的意思。也可以提前通过取消函数进行取消。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

// 为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到,这是我们实际用经常要用到的技巧,一般我们想要通过上下文来传递数据时,可以通过这个方法,如我们需要tarce追踪系统调用栈的时候
func WithValue(parent Context, key, val interface{}) Context

定时器

time.Timer

Timer的结构定义

type Timer struct {
    C <-chan Time
    r runtimeTimer
}

其他方法

  • func After(d Duration) <-chan Time { return NewTimer(d).C }

根据源码可以看到After直接是返回了Timerchannel,这种就可以做超时处理。 比如我们有这样一个需求:我们写了一个爬虫,爬虫在HTTP GET 一个网页的时候可能因为网络的原因就一只等待着,这时候就需要做超时处理,比如只请求五秒,五秒以后直接丢掉不请求这个网页了,或者重新发起请求。

Get("http://baidu.com/")
func Get(url string) {
    response := make(chan string)
    response = http.Request(url)
    select {
    case html :=<- response:
        println(html)
    case <-time.After(time.Second * 5):
        println("超时处理")
    }
}

可以从代码中体现出来,如果五秒到了,网页的请求还没有下来就是执行超时处理,因为Timer的内部会是帮你在你设置的时间长度后自动向Timer.C中写入当前时间。

其实也可以写成这样:

func Get(url string) {
    response := make(chan string)
    response = http.Request(url)
    timeOut := time.NewTimer(time.Second * 3)
    select {
    case html :=<- response:
        println(html)
    case <-timeOut.C:
        println("超时处理")
    }
}
  • func (t *Timer) Reset(d Duration) bool//强制的修改timer中规定的时间,Reset会先调用 stopTimer再调用 startTimer,类似于废弃之前的定时器,重新启动一个定时器,ResetTimer还未触发时返回true;触发了或Stop了,返回false

  • func (t *Timer) Stop() bool// 如果定时器还未触发,Stop会将其移除,并返回 true;否则返回 false;后续再对该 Timer调用 Stop,直接返回 false

  • func AfterFunc(d Duration, f func()) *Timer // 在时间d后自动执行函数f

    func main() {
        f := func(){fmt.Println("I Love You!")}
        time.AfterFunc(time.Second*2, f)
        time.Sleep(time.Second * 4)
    
    }
    

time.Ticker

其实Ticker就是一个重复版本的Timer,它会重复的在时间d后向Ticker中写数据

  • func NewTicker(d Duration) *Ticker // 新建一个Ticker

  • func (t *Ticker) Stop() // 停止Ticker

  • func Tick(d Duration) <-chan Time // Ticker.C 的封装