# 开发常用 ## 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` ```go 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() ` 做为根节点 ```go //传递一个父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 ``` ### 参考如下 [golang服务器开发利器 context用法详解](https://www.jianshu.com/p/d24bf8b6c869) [Golang Context深入理解](https://juejin.im/post/5a6873fef265da3e317e55b6) ## 定时器 ### time.Timer `Timer`的结构定义 ```go type Timer struct { C <-chan Time r runtimeTimer } ``` #### 其他方法 - `func After(d Duration) <-chan Time { return NewTimer(d).C }` 根据源码可以看到`After`直接是返回了`Timer`的`channel`,这种就可以做超时处理。 比如我们有这样一个需求:我们写了一个爬虫,爬虫在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`,类似于废弃之前的定时器,重新启动一个定时器,`Reset`在 `Timer`还未触发时返回`true`;触发了或`Stop`了,返回`false`。 - `func (t *Timer) Stop() bool`// 如果定时器还未触发,`Stop`会将其移除,并返回 `true`;否则返回 `false`;后续再对该 `Timer`调用 `Stop`,直接返回 `false`。 - `func AfterFunc(d Duration, f func()) *Timer` // 在时间d后自动执行函数f ```go 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 的封装 ### 参考 [Golang time.Timer and time.Ticker](https://www.jianshu.com/p/2b4686b8de4a)