跳到主要内容

Context

提示

w为了解决 不同的goroutine之间同步特定数据、取消信号以及处理请求的截止日期

context.Context

context 的三个用途

  • 发送取消信号
  • 设置超时时间
  • 存储/检索请求的相关值

发送取消信号

context.Context提供了一种机制,可以发送一个信号,告诉收到context的进程停止。

当一个服务器收到关闭信号时,它需要"优雅地"停止;如果它正在处理一个请求,它需要在关闭之前为其提供服务。context包提供了context.WithCancel API,它返回一个配置了cancel的新上下文和一个取消它的函数。如果你调用cancel函数,信号会被发送到接收该上下文的进程中

package main
import (
"fmt"
"time"
"syscall"
"context"
"log"
"os/signal"

)
func main(){
ctx,cancel:= context.WithCancel(context.Background())

defer cancel()
go func(){
signChan:=make(chan os.Signal,1)
signal.Notify(signChan,syscall.SIGHUP,syscall.SIGNINT,sign.SIGTERM)
<-signChan
cancel()
}()
svr :=Run(ctx)
log.Println("服务停止")
}

func Run(ctx context.Context){
for {
select{
case ctx.Done():
log.Println("取消服务")
return
default:
handleRequest()
}
}
}
func handleRequest(){
fmt.Println("服务进行中")
}

设置超时时间

操作设置超时。想象一下,你正在向第三方发送HTTP请求。如果由于某些原因,如网络中断,请求的时间超过预期,你可能想取消请求,以防止整个过程挂起。通过context.WithTimeout,你可以为这些情况设置超时。

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() // ← cancel should be called even if timeout didn't happen

SendRequest(ctx) // ← subroutine that can get stuck
}


func SendRequest(ctx context.Context) {
respCh := make(chan interface{}, 1)
go sendRequest(respCh)

select {
case <-ctx.Done():
log.Println("operation timed out!")
case <-respCh:
log.Println("response received")
}
}

func sendRequest(ch chan<- interface{}) {
time.Sleep(60 * time.Second)
ch <- struct{}{}
}


context包也有context.WithDeadline();不同的是,context.WithTimeout需要time.Duration,而context.WithDeadline()需要time.Time

存储/检索请求的相关值

上下文的最后一种用法是在上下文中存储和检索与请求相关的值。例如,如果服务器收到一个请求,你可能希望在请求过程中产生的所有日志行都有请求信息,如路径和方法。在这种情况下,你可以创建一个日志记录器,设置请求相关的信息,并使用context.WithValue将其存储在上下文中


var logCtxKey = &struct{}{}

func handleRequest(w http.ResponseWriter, r *http.Request) {
method, path := r.Method, r.URL.Path
logger := log.With().
Str("method", method).
Str("path", path).
Logger()
ctxWithLogger := context.WithValue(r.Context(), logCtxKey, logger)
...
accessDatabase(ctxWithLogger)
}
func accessDatabase(ctx context.Context) {
logger := ctx.Value(logCtxKey).(zerolog.Logger)
logger.Debug().Msg("accessing database")
}