Go并发模式之context(比done channel 更加强大)-取消goroutine的好的解决方案

在并发程序中, 由于超时、取消、或系统的其他部分的故障 往往需要抢占操作。
done channel 可以在你的程序中流动并取消所有阻塞的并发操作。 看起来不错,但是还不完美,还不是很够用。
什么样的功能还需要呢?什么样功能才算完美呢?
如果我们可以在简单的通知上附加传递额外的信息; 如 为什么取消发生, 函数是否有需要完成的最后期限(超时), 这些情况下这些功能 非常有用。
context 包有如下方法:
todo


context(type 类型): 有:
一个Deadline 函数,用于指示在一定时间之后goroutine 是否被取消,
一个 Err方法, 如果goroutine 被取消,将返回非零。
一个Value 方法,
函数中取消有三种情况:

1。 goroutine 的 父 goroutine 可能想要取消它。
2。 一个goroutine 可能想要取消它的 子goroutine
3。 goroutine 中呢任何阻塞操作都必须是可抢占的, 以便它可以被取消。
下面看看 之前 使用 do channel 方式 有什么优缺点

func printGreeting(done <-chan interface{}) error{
	greeting, err := genGreeting(done)
	if err != nil{
		return err
	}
	fmt.Printf("%s world!\n", greeting)
	return nil
}

func genGreeting(done <-chan interface{})(string, error){
	switch locale, err := locale(done);{
	case err != nil:
		return "", err
	case locale == "EN/US":
		return "hello", nil
	}
	return "", fmt.Errorf("unsupported locale")
}

func locale(done <-chan interface{})(string, error){
	select{
	case <-done :
		return "", fmt.Errorf("canceled")
	case <-time.After(1*time.Minute):
	}
	return "EN/US", nil
}

func printFarewell(done <-chan interface{})  error{
	farewell, err := genFarewell(done)
	if err != nil{
		return err
	}
	fmt.Printf("%s world!\n", farewell)
	return nil
}

func genFarewell(done <-chan interface{})(string, error){
	switch locale,err := locale(done);{
	case err != nil:
		return "", err
	case locale == "EN/US":
		return "goodbye", nil
	}
	return "", fmt.Errorf("unsupproted locale")
}


func main() {

	var wg sync.WaitGroup
	done := make(chan interface{})
	defer close(done)

	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := printGreeting(done); err != nil{
			fmt.Println("%v", err)
			return
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := printFarewell(done); err != nil{
			fmt.Printf("%v", err)
		}
	}()

	wg.Wait()
}
假设有这样需求:
假设genGreeting 只想在放弃调用locale之前等待 1s(超过1s 就不想调用了),超时时间 1s;
如果 printGreeting 不成功,我们也想取消 对 printFarewall的调用。 毕竟不打招呼,说再见就没有意义了。使用do channel 难以做到这点,下面 看看context 能不能满足这样的需求? 
func main() {
	var wg sync.WaitGroup
	//Background() 方法 返回一个空的上下文
	// withCancel 包装它 为了 获取 cancel/取消 方法/功能
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	wg.Add(1)
	go func() {
		defer wg.Done();
		if err := printGreeting(ctx); err != nil{
			fmt.Printf("cannot print greeting: %v\n", err)

			//在这一行,如果打印问候出错,将 取消上下文;  或说对上下文 环境 执行 cancel, 这样使用这个上下文或者从这个上下文衍生出来的上下文。
			//中  ct.Done()  将收到信号/收到值; 进而达到解除阻塞 的目的。
			cancel()
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := printFarewell(ctx); err != nil{
			fmt.Printf("cannot print farewell: %v\n", err)
		}
	}()
	wg.Wait()

}


func printGreeting(ctx context.Context) error{
	greeting, err := genGreeting(ctx)
	if err != nil{
		return err
	}
	fmt.Printf("%s world!\n", greeting)
	return nil
}

func genGreeting(ctx context.Context)(string, error){
	ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
	defer cancel()
	switch locale, err := locale(ctx);{
	case err != nil :
		return "", err
	case locale == "EN/US":
		return "hello", nil
	}
	return "", fmt.Errorf("unsupported locale")
}


func locale(ctx context.Context)(string, error){
	select{
	case <-ctx.Done():
		return "", ctx.Err()
	case <-time.After(1* time.Minute) :
	}
	return "EN/US", nil
}


func printFarewell(ctx context.Context) error{
	farewell, err := genFarewell(ctx)
	if err != nil{
		return err
	}
	fmt.Printf("%s world!\n", farewell)
	return nil
}

func genFarewell(ctx context.Context)(string, error){
	switch locale, err := locale(ctx);{
	case err != nil:
		return "", err
	case locale == "EN/US":
		return "goodbye", nil
	}
	return "", fmt.Errorf("unsupported locale")
}
//cannot print greeting: context deadline exceeded
//cannot print farewell: context canceled
总结: cancel()  或者 时间到了(context.WithTimeout(ctx, 1*time.Second)) 都会 使 ctx.Done() 收到值, 解除阻塞
那么 Deadline 又是在什么情况下。或有那些需求时会用到?
func locale(ctx context.Context)(string, error){
	if deadline, ok := ctx.Deadline(); ok{
		if deadline.Sub(time.Now().Add(1*time.Minute)) <= 0 {
			fmt.Println("end locale")
			return "", context.DeadlineExceeded
		}
	}

	select{
	case <- ctx.Done():
		return "", ctx.Err()
	case <-time.After(1*time.Minute):
	}
	return "EN/US", nil
}
//end locale
//cannot print greeting: context deadline exceeded
//cannot print farewell: context canceled

/**
在这里我们检查我们的上下文是否提供了截止。 如果提供了,并且 能够确定 当前时间 再执行下去一定会超时, 那么程序就立刻返回,不往下走业务逻辑;
可以返回上下文包中的特定错误, 即 DeadlineExceeded

在调用下一个 成本很高的函数的程序中, 这可能会节省大量的时间,它允许 该函数立即失败,而不必等待实际的超时发生。 前提/适用的情况是 必须知道
下级调用图 需要多长时间,这个可能实践起来非常困难。
图:todo

实际上 超时时间设置 肯定大于 正常情况下 程序的执行时间;如 假如 locale 一般 要执行 1min ;那么超时时间 就是 1min + ;(本例子中 是为了看到
效果 才将 超时时间设置为1s)
决定 deadline 是否满足 <=0 的关键 在于 now位置(它是变化的);实际上 意思是: 如果前面执行的时间太长了(出现意外情况);那么后面也就没必要
再执行了,因为执行了肯定会超时的!
如何在上下文中存储数据以及如何检索数据:
func main(){
	ProcessRequest("jane", "abc123")
}

func ProcessRequest(userID, authToken string){
	ctx := context.WithValue(context.Background(), "userID", userID)
	ctx = context.WithValue(ctx, "authToken", authToken)
	HandleResponse(ctx)
}


func HandleResponse(ctx context.Context){
	fmt.Printf("handling response for %v (%v)",
		ctx.Value("userID"), ctx.Value("authToken"))
}
存储 使用 context.WithValue(....);  检索使用 ctx.Value()
正确使用这个功能 ,需要注意的是:/要满足的条件是:
1。 使用的键 必须满足 可比较特性/特点; 也就是 使用运算符== 和 != 在使用时要返回正确的结果
2。 返回值必须安全, 才能从多个goroutine 访问。

具体怎么做呢?
建议: 在包中定义一个自定义键 类型; 另一个包中定义另一个自定义键类型,就可以防止上下文中的冲突。
func main(){
	type foo int
	type bar int

	m := make(map[interface{}]int)
	m[foo(1)] = 1
	m[bar(1)] = 2

	fmt.Printf("%v", m)
}

//map[1:1 1:2]
由于 我们 不导出用于存储数据的key, 所以 通常是 必须导出 检索数据的函数。 这样 使用者 可以使用静态,类型安全的函数。
func main(){
	ProcessRequest("jane", "abc123")
}

type ctxKey int

const(
	ctxUserID ctxKey = iota
	ctxAuthToken
)

func UserID(c context.Context) string{
	return c.Value(ctxUserID).(string)
}

func AuthToken(c context.Context) string{
	return c.Value(ctxAuthToken).(string)
}

func ProcessRequest(userID, authToken string){
	ctx := context.WithValue(context.Background(), ctxUserID, userID)
	ctx = context.WithValue(ctx, ctxAuthToken, authToken)
	HandleResponse(ctx)
}

func HandleResponse(ctx context.Context){
	fmt.Printf(" handling response for %v (auth:%v)", UserID(ctx), AuthToken(ctx))
}
//handling response for jane (auth:abc123)
context 的实例中该存储什么? 什么样数据存在context 中合适?
1。
2。 数据应该是不可变的
3。 数据应该 简单类型的
4。 数据就是数据,不是类型与方法
5。 尽量不要这么用数据;如 根据context 中包含 与不包含 算法而不同,这种情况就要考虑 了,要不要把这样数据放在context中?

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值