Context深化解读-Go言语初级个性 (contextual)
概述
在Go言语中,context(高低文)是一个十分关键的概念。
本文将讨论Go言语中context的用法,从基础概念到实践运行,将片面了解高低文的经常使用方法。
关键内容包括
什么是Context(高低文)
Context的基本用法:创立与传递
Context的敞开与超时
Context的值传递
实践运行场景:HTTP恳求的Context经常使用
数据库操作中的Context运行
自定义Context的成功
Context的生命周期治理
Context的留意事项
1.什么是Context(高低文)
在Go言语中,context(高低文)是一个接口,定义了四个方法:Deadline()、Done()、Err()和Value(keyinterface{})。
2.Context的基本用法:创立与传递
2.1创立Context
packagemnimport("context""fmt")funcmain(){//创立一个空的Contextctx:=context.Background()//创立一个带有敞开信号的Context_,cancel:=context.WithCancel(ctx)defercancel()//提前调用cancel,确保在函数分开时敞开Contextfmt.Println("Context创立成功")}
以上代码展示了如何创立一个空的context和一个带有敞开信号的context。
经常使用context.WithCancel(parent)可以创立一个带有敞开信号的子context。
在调用cancel函数时,一切基于该context的操作都会收到敞开信号。
2.2传递Context
packagemainimport("context""fmt""time")funcworker(ctxcontext.Context){for{select{case<-ctx.Done():fmt.Println("收到敞开信号,义务完结")returndefault:fmt.Println("正在口头义务...")time.Sleep(1*time.Second)}}}funcmain(){ctx:=context.Background()ctxWithCancel,cancel:=context.WithCancel(ctx)defercancel()goworker(ctxWithCancel)time.Sleep(3*time.Second)cancel()//发送敞开信号time.Sleep(1*time.Second)}
在下面例子中,创立了一个带有敞开信号的context,并将它传递给一个goroutine中的义务。
调用cancel函数,可以发送敞开信号,终止义务的口头。
3.Context的敞开与超时
3.1经常使用Context成功敞开
packagemainimport("context""fmt""time")funcmain(){ctx:=context.Background()ctxWithCancel,cancel:=context.WithCancel(ctx)gofunc(){select{case<-ctxWithCancel.Done():fmt.Println("收到敞开信号,义务完结")case<-time.After(2*time.Second):fmt.Println("义务成功")}}()time.Sleep(1*time.Second)cancel()//发送敞开信号time.Sleep(1*time.Second)}
在这个例子中,经常使用time.After函数模拟一个长期间运转的义务。
假设义务在2秒内成功,就打印义务成功,否则在接纳到敞开信号后打印收到敞开信号,义务完结。
3.2经常使用Context成功超时控制
packagemainimport("context""fmt""time")funcmain(){ctx:=context.Background()ctxWithTimeout,cancel:=context.WithTimeout(ctx,2*time.Second)defercancel()select{case<-ctxWithTimeout.Done():fmt.Println("超时,义务完结")case<-time.After(1*time.Second):fmt.Println("义务成功")}}
在下面例子中,经常使用context.WithTimeout(parent,duration)函数创立了一个在2秒后智能敞开的context
假设义务在1秒内成功,就打印义务成功,否则在2秒后打印超时,义务完结。
4.Context的值传递
4.1经常使用WithValue传递值
packagemainimport("context""fmt")typekeystringfuncmain(){ctx:=context.WithValue(context.Background(),key("name"),"Alice")value:=ctx.Value(key("name"))fmt.Println("Name:",value)}
在下面例子中,经常使用context.WithValue(parent,key,value)函数在context中传递了一个键值对。
经常使用ctx.Value(key)函数可以失掉传递的值。
5.实践运行场景:HTTP恳求的Context经常使用
5.1经常使用Context处置HTTP恳求
packagemainimport("fmt""/http""time")funchandler(whttp.ResponseWriter,r*http.Request){ctx:=r.Context()select{case<-time.After(3*time.Second):fmt.Fprintln(w,"Hello,World!")case<-ctx.Done():err:=ctx.Err()fmt.Println("处置恳求:",err)http.Error(w,err.Error(),http.StatusInternalServerError)}}funcmain(){http.HandleFunc("/",handler)http.ListenAndServe(":8080",nil)}
在下面例子中,经常使用http.Request结构体中的Context()方法失掉到恳求的context,并在处置恳求时设置了3秒的超时期间。
假设恳求在3秒内成功,就前往Hello,World!,否则前往一个失误。
5.2处置HTTP恳求的超时与敞开
packagemainimport("context""fmt""net/http""time")funchandler(whttp.ResponseWriter,r*http.Request){ctx,cancel:=context.WithTimeout(r.Context(),2*time.Second)defercancel()select{case<-time.After(3*time.Second):fmt.Fprintln(w,"Hello,World!")case<-ctx.Done():err:=ctx.Err()fmt.Println("处置恳求:",err)http.Error(w,err.Error(),http.StatusInternalServerError)}}funcmain(){http.HandleFunc("/",handler)http.ListenAndServe(":8080",nil)}
在下面例子中,经常使用context.WithTimeout(parent,duration)函数为每个恳求设置了2秒的超时期间。
假设恳求在2秒内成功,就前往Hello,World!,否则前往一个失误。
6.数据库操作中的Context运行
6.1经常使用Context控制数据库查问的超时
packagemainimport("context""database/sql""fmt""time"_".com/go-sql-driver/")funcmain(){db,err:=sql.Open("mysql","username:pass@tcp(localhost:3306)/database")iferr!=nil{fmt.Println(err)return}deferdb.Close()ctx,cancel:=context.WithTimeout(context.Background(),2*time.Second)defercancel()rows,err:=db.QueryContext(ctx,"SELECT*FROMusers")iferr!=nil{fmt.Println("查问出错:",err)return}deferrows.Close()//处置查问结果}
在下面例子中,经常使用context.WithTimeout(parent,duration)函数为数据库查问设置了2秒的超时期间,确保在2秒内成功查问操作。
假设查问超时,程序会收到context的敞开信号,从而终止数据库查问。
6.2经常使用Context敞开长期间运转的数据库事务
packagemainimport("context""database/sql""fmt""time"_"github.com/go-sql-driver/mysql")funcmain(){db,err:=sql.Open("mysql","username:password@tcp(localhost:3306)/database")iferr!=nil{fmt.Println(err)return}deferdb.Close()tx,err:=db.Begin()iferr!=nil{fmt.Println(err)return}ctx,cancel:=context.WithTimeout(context.Background(),2*time.Second)defercancel()gofunc(){<-ctx.Done()fmt.Println("接纳到敞开信号,回滚事务")tx.Rollback()}()//口头长期间运转的事务操作//...err=tx.Commit()iferr!=nil{fmt.Println("提交事务出错:",err)return}fmt.Println("事务提交成功")}
在这个例子中,经常使用context.WithTimeout(parent,duration)函数为数据库事务设置了2秒的超时期间。
同时经常使用goroutine监听context的敞开信号。
在2秒内事务没有成功,程序会收到context的敞开信号,从而回滚事务。
7.自定义Context的成功
7.1成功自定义的Context类型
packagemainimport("context""fmt""time")typeMyContextstruct{context.ContextRequestIDstring}funcWithRequestID(ctxcontext.Context,requestIDstring)*MyContext{return&MyContext{Context:ctx,RequestID:requestID,}}func(c*MyContext)Deadline()(deadlinetime.Time,okbool){returnc.Context.Deadline()}func(c*MyContext)Done()<-chanstruct{}{returnc.Context.Done()}func(c*MyContext)Err()error{returnc.Context.Err()}func(c*MyContext)Value(keyinterface{})interface{}{ifkey=="requestID"{returnc.RequestID}returnc.Context.Value(key)}funcmain(){ctx:=context.Background()ctxWithRequestID:=WithRequestID(ctx,"12345")requestID:=ctxWithRequestID.Value("requestID").(string)fmt.Println("RequestID:",requestID)}
在这个例子中,成功了一个自定义的MyContext类型,它蕴含了一个RequestID字段。
经过WithRequestID函数,可以在原有的context上附加一个RequestID值,而后在须要的时刻失掉这个值。
7.2自定义Context的运行场景
自定义context类型的运行场景十分宽泛,比如在微服务架构中,
8.Context的生命周期治理
8.1Context的生命周期
context.Context是无法变的,一旦创立就不能修正。它的值在传递时是安保的,可以被多个goroutine共享。
在一个恳求处置的生命周期内,通常会创立一个顶级的Context,而后从这个顶级的Context派生出子Context,传递给详细的处置函数。
8.2Context的敞开
当恳求处置成功或许出现失误时,应该被动调用context.WithCancel或context.WithTimeout等函数创立的Context的Cancel函数来通知子goroutines中止上班。
这样可以确保资源被及时监禁,防止goroutine走漏。
funchandleRequest(ctxcontext.Context){//派生一个新的Context,设置超时期间ctx,cancel:=context.WithTimeout(ctx,time.Second)defercancel()//确保在函数分开时调用cancel,防止资源走漏//在这个新的Context下启动操作//...}
8.3Context的传递
在函数之间传递Context时,确保传递的是必要的最小消息。防止在函数间传递整个Context,而是选用传递Context的衍动物。
如context.WithValue创立的Context,其中蕴含了特定的键值对消息。
funcmiddleware(nexthttp.Handler)http.Handler{returnhttp.HandlerFunc(func(whttp.ResponseWriter,r*http.Request){//从恳求中失掉特定的消息userID:=extractUserID(r)//将特定的消息存储到Context中ctx:=context.WithValue(r.Context(),userIDKey,userID)//将新的Context传递给下一个处置器next.ServeHTTP(w,r.WithContext(ctx))})}
9.Context的留意事项
9.1不要在函数签名中传递Context
防止在函数的参数列表中传递context.Context,除非确实须要用到它。
假设函数的逻辑只有要经常使用Context的局部性能,那么最好只传递它须要的详细消息,而不是整个Context。
//不介绍的做法funcprocessRequest(ctxcontext.Context){//...}//介绍的做法funcprocessRequest(userIDint,timeouttime.Duration){//经常使用userID和timeout,而不是整个Context}
9.2防止在结构体中滥用Context
不要在结构体中保留context.Context,由于它会参与结构体的复杂性。
若是须要在结构体中经常使用Context的值,最好将须要的值作为结构体的字段传递出去。
typeMyStructstruct{//不介绍的做法Ctxcontext.Context//介绍的做法UserIDint}
golang自动更新怎么实现
首先理解是错的,不管用户态的API(syscall)是否是同步还是异步,在kernel层面都是异步的。 其实实现原理很简单,就是利用C(嵌入汇编)语言可以直接修改寄存器(setcontext/setjmp/longjmp均是类似原理,修改程序指针eip实现跳转,栈指针实现上线文切换)来实现从func_a调进去,从func_b返回出来这种行为。 对于golang来说,func_a/func_b属于不同的goroutine,从而就实现了goroutine的调度切换。 另外对于所有可能阻塞的syscall,golang对其进行了封装,底层实际是epoll方式做的,注册回调后切换到另一个runnable的goroutine。
(知乎) golang的goroutine是如何实现的
Go runtime的调度器:在了解Go的运行时的scheduler之前,需要先了解为什么需要它,因为我们可能会想,OS内核不是已经有一个线程scheduler了嘛?熟悉POSIX API的人都知道,POSIX的方案在很大程度上是对Unix process进场模型的一个逻辑描述和扩展,两者有很多相似的地方。 Thread有自己的信号掩码,CPU affinity等。 但是很多特征对于Go程序来说都是累赘。 尤其是context上下文切换的耗时。 另一个原因是Go的废品回收需要所有的goroutine停止,使得内存在一个一致的状态。 废品回收的时间点是不确定的,如果依靠OS自身的scheduler来调度,那么会有大量的线程需要停止工作。 单独的开发一个GO得调度器,可以是其知道在什么时候内存状态是一致的,也就是说,当开始废品回收时,运行时只需要为当时正在CPU核上运行的那个线程等待即可,而不是等待所有的线程。 用户空间线程和内核空间线程之间的映射关系有:N:1,1:1和M:NN:1是说,多个(N)用户线程始终在一个内核线程上跑,context上下文切换确实很快,但是无法真正的利用多核。 1:1是说,一个用户线程就只在一个内核线程上跑,这时可以利用多核,但是上下文switch很慢。 M:N是说, 多个goroutine在多个内核线程上跑,这个看似可以集齐上面两者的优势,但是无疑增加了调度的难度。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。