初识context
首先引入context的问题:我们如何在主goroutine(main)中通知其他的子goroutine退出,我们想到了三种解决办法如下
1. 定义全局变量
2. 设置channel
3. context
问题: 我们要在子gorotine中打印一段话,经过5s后main函数通知子goroutine全部退出
1.定义全局变量的方式
//声明全局变量作为退出的信号
var exit bool
func f1() {
for{
fmt.Println("context")
time.Sleep(time.Millisecond * 500 )
if exit {
break
}
}
}
func main() {
go f1()
time.Sleep(time.Second * 5 )
exit = true
}
2.channel方式
//声明全局的channel 并且对全局的channel进行初始化(一定要初始化)
var exitChan = make(chan bool,1)
func f1() {
//LOOP设置跳转 break仅仅只能跳出select语句
LOOP:
for{
fmt.Println("context")
time.Sleep(time.Millisecond * 500 )
//select 语句 随机的执行下面两个case语句 哪个满足条件执行哪一个
select {
case <- exitChan:
break LOOP
default:
}
}
}
func main(){
go f1()
time.Sleep(time.Second * 5 )
//向通道中传入true
exitChan <- true
}
3.context方式
func f2(ctx context.Context){
LOOP:
for {
fmt.Println("context son")
time.Sleep(time.Millisecond * 500)
select{
case <- ctx.Done():
break LOOP
default:
}
}
}
func f1(ctx context.Context) {
//在f1中启动另一个goroutine
go f2(ctx)
LOOP:
for {
fmt.Println("context father")
time.Sleep(time.Millisecond * 500)
select{
//Context.Done() 返回一个结构体类型的channel
case <- ctx.Done():
break LOOP
default:
}
}
}
func main(){
//cintext.WithCancel传递的是父节点 context.Background()
ctx , cancel := context.WithCancel(context.Background())
go f1(ctx)
time.Sleep(time.Second * 5 )
cancel()
}
说明:
context.WithCancel()传入的但是是父节点
返回context.Context对象和cancel函数
当执行cancel函数时,会向Context所有的子节点的Context.Done()返回的channel中发送一个struct{}空结构体 通知其子节点结束
context的优点:
1.使得协同开发时信号传递方式的统一
2.http服务端开发时 可以通过context控制连接超时(比如一个server端会启动多个gorotinue去访问后端的资源,我们设置超时时间时是无法进行跟踪的),我们使用context,如果超过了超时时间,所有的goroutine都会收到信号 进行退出.
Context的基本使用
前面介绍了context的WithCancel方法,下面进行一些扩展:
1.WithCancel
2.WithDeadlin
3.WithTimeout
4.WithValue
Context对象:
是一个接口类型 类型中定义了四个方法
Package:
context
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
1.WithCancel用法
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
当执行cancel()函数时 向ctx.Done()返回的channel中发送信号(struct{})
func f1(ctx context.Context) {
LOOP:
for {
fmt.Println("chenjunde nb")
time.Sleep(time.Millisecond * 500 )
select {
case <-ctx.Done():
break LOOP
default:
}
}
}
func main() {
ctx , cancel := context.WithCancel(context.Background())
go f1(ctx)
time.Sleep(time.Second * 5)
cancel()
}
2.WithDeadline和WithTimeout
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) deadline指定的是相对时间
func main() {
//WithDeadline传入的是相对时间 当时间到达d时会向ctx.Done()发送signal
d := time.Now().Add(50 * time.Millisecond)
ctx , cancel := context.WithDeadline(context.Background(),d)
defer cancel()
select {
//func After(d Duration) <-chan Time time.After当时间到时 返回当前时间对象的channel
case <- time.After(time.Second):
fmt.Println("chenjunde nb")
case <- ctx.Done():
fmt.Println(ctx.Err())
}
}
说明:尽管context会过期 但是最好还是要调用cancel,因为使用deadline或者timeout可能使上下文存活的时间超过必要的时间
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
模拟数据库连接:
var wg sync.WaitGroup
func Connect(ctx context.Context) {
LOOP:
for {
fmt.Println("connect db ...")
time.Sleep(time.Millisecond * 10 )
select {
case <- ctx.Done():
break LOOP
default:
}
}
fmt.Println("worker done")
defer wg.Done()
}
func main() {
//模拟数据库连接超时
//当时间超过d参数时会自动结束 这个使用的时绝对时间WithTimeout
ctx , cancel := context.WithTimeout(context.Background(),time.Millisecond * 50 )
wg.Add(1)
go Connect(ctx)
time.Sleep(time.Second * 5)
cancel()
wg.Wait()
fmt.Println("over")
}
3.WithValue
func WithValue(parent Context, key, val interface{}) Context
所提供的键key必须是可比较的 并且比应该是任何内置类型,以避免上下文在包之间发生冲突
type TraceCode string
var wg sync.WaitGroup
func f1(ctx context.Context) {
key := TraceCode("chenjunde")
traceCode, ok := ctx.Value(key).(string) // 类型断言 获取key值
if !ok {
fmt.Println("invalid trace code")
}
LOOP:
for {
fmt.Printf("worker, trace code:%s\n", traceCode)
time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
select {
case <-ctx.Done(): // 50毫秒后自动调用
break LOOP
default:
}
}
fmt.Println("worker done!")
wg.Done()
}
func main() {
//设置50ms的超时
ctx , cancel := context.WithTimeout(context.Background(),time.Millisecond* 50 )
//向子goroutine 传递值
ctx = context.WithValue(context.Background(),TraceCode("chenjunde"),"0000001")
wg.Add(1)
go f1(ctx)
time.Sleep(time.Second * 5 )
cancel()
wg.Wait()
fmt.Println("over")
}