Context

初识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")
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值