《7天学会Go并发编程》第五天 go语言Context的应用和实现 go实现超时判断

《7天学会Go并发编程》第五天 go语言Context的应用和实现 go实现超时判断,第1张

目录​​​​

前言

一、Context是什么?

二、Context使用案例

1.使用Context CancelFunc限制线程启动次数

2.Derived Context

3 使用Context实现超时取消

4 使用Context实现主动超时取消

总结



前言

        前面的文章主要介绍了sync.WaitGroup类型:主要讲如何通过waitGroup实现一对多的goroutline协作流程。通俗的说就是,使用一个线程等待其他线程完成。

waitGroup:主要的使用方法:1、先add需要等待的线程数 2、主线程阻塞,等待并发线程执行3、主线程阻塞等待并发线程执行完毕

使用waitGroup的劣势:

        1、只能实现一对多的线程等待,不能支撑嵌套的层次等待

        2、使用waitGroup时,需要注意并发线程数和线程等待数一致,不一致的话,就会引起panic

         针对上述问题,go预言提出了context,实现线程数不定的多线程阻塞等待,可以实现多线程的依赖等待。


一、Context是什么?

        Context类型是context包的核心类型,能够在函数调用的过程之中传递结束信息,请求范围的值、截止时间。

        代码如下:其中Done返回一个信号结束的channel,Deadline返回一个任务停止执行的截止日期,Value则返回一个可以被多个线程安全使用的Value。  

// A Context carries a deadline, cancellation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
    // Done returns a channel that is closed when this Context is canceled
    // or times out.
    Done() <-chan struct{}

    // Err indicates why this context was canceled, after the Done channel
    // is closed.
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}
}
二、Context使用案例 1.使用Context CancelFunc限制线程启动次数

使用Context限制线程开启次数,下面的代码实现线程开启12个后,便主动结束所有线程。

func main() {
	contextExample()
}

func contextExample() {
	total := 12
	var num int32
	fmt.Printf("The number: %d [with context.Context]\n", num)
	cxt, cancelFunc := context.WithCancel(context.Background())
	for i := 1; i <= total; i++ {
		go addNum1(&num, i, func() {
			if atomic.LoadInt32(&num) == int32(total) {
				cancelFunc()
			}
		})
	}
	<-cxt.Done()
	fmt.Println("End.")
}


func addNum1(numP *int32, id int, deferFunc func()) {

	for i := 0; ; i++ {
		deferFunc()
		time.Sleep(time.Millisecond * 200)
		atomic.AddInt32(numP,1)
		fmt.Printf("累计线程启动次数: %d 线程序号:%d-打印值:%d\n", atomic.LoadInt32(numP), id, i)
	}
}
2.Derived Context

go语言的实际开发过程中,context也是可以传递的。从parentContext向下看,完整Context形成一颗多叉树。

前往讲过Context是可以层级取消的,这里主要距离说明一下Context的衍生或者Derived案例。

package main

import (
	"context"
	"fmt"
	"sync/atomic"
	"time"
)

func main() {
	layer1 := context.WithValue(context.Background(), "key1", "layer:layer1 v->value1")
	layer2 := context.WithValue(layer1, "key2", "layer:layer2 v->value2")
	value1 := layer2.Value("key1")
	value2 := layer2.Value("key2")
	fmt.Print(value1)
	fmt.Println()
	fmt.Println(value2)
}

 输出如下,layer2作为layer1衍生出来的context,携带了这个分支上所有的值,通过Value函数的方式获取了属于layer1的value值。


layer:layer1 v->value1
layer:layer2 v->value2

Process finished with the exit code 0
3 使用Context实现超时取消

超时取消如何实现呢?实际开发过程中,接口超时断开是一个常见功能。那么在go语言中如何实现呢?

主线程在func1启动,睡眠2S后,执行ctx取消。func1当中也有自身的慢执行停止逻辑,出于演示 ,让主线程代码先停止。实际应用过程中,可根据需求来进行相应编程。

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func func1(ctx context.Context, wg *sync.WaitGroup) error {
	defer wg.Done()
	respC := make(chan int)
	// 处理逻辑
	go func() {
		time.Sleep(time.Second * 5)
		respC <- 10
	}()
	// 取消机制
	select {
	//等于ctx的超时退出
	case <-ctx.Done():
		fmt.Println("cancel")
		return nil
	//等待睡眠5S的线程退出	
	case r := <-respC:
		fmt.Println(r)
		return nil
	}
}

func main() {
	wg := new(sync.WaitGroup)
	ctx, cancel := context.WithCancel(context.Background())
	wg.Add(1)
	go func1(ctx, wg)
	time.Sleep(time.Second * 2)
	// 触发取消
	cancel()
	// 等待goroutine退出
	wg.Wait()
}
4 使用Context实现主动超时取消
func main() {
	timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*10)
	defer cancelFunc() // releases resources if slowOperation completes before timeout elapses
	slowOperation(timeout)
	fmt.Println("都停止了")
}

func slowOperation(ctx context.Context) {
	go func() {
		for i := 0; ; i++ {
			time.Sleep(1*time.Second)
			fmt.Printf("%s 肆意打印:%d\n",time.Now(),i)
		}
	}()
	select {
	case <-ctx.Done():
		fmt.Println("deadline已到")
	}
}

对应的结果:可以看出一个执行死循环的go线程活了十秒以后,被强制终结。

2022-05-14 16:47:56.7303913 +0800 CST m=+1.020835201 肆意打印:0
2022-05-14 16:47:57.8223593 +0800 CST m=+2.112803201 肆意打印:1
2022-05-14 16:47:58.8311153 +0800 CST m=+3.121559201 肆意打印:2
2022-05-14 16:47:59.8427716 +0800 CST m=+4.133215501 肆意打印:3
2022-05-14 16:48:00.8550931 +0800 CST m=+5.145537001 肆意打印:4
2022-05-14 16:48:01.8710894 +0800 CST m=+6.161533301 肆意打印:5
2022-05-14 16:48:02.8788328 +0800 CST m=+7.169276701 肆意打印:6
2022-05-14 16:48:03.8899483 +0800 CST m=+8.180392201 肆意打印:7
2022-05-14 16:48:04.9039227 +0800 CST m=+9.194366601 肆意打印:8
deadline已到
都停止了

总结

         context 的实现相对来说比较复杂,但在实际使用中能给大家带来不小的便利。本篇博客结合博主自身的使用经验,介绍一下Context的使用方法和使用场景。欢迎大家对我的博客提出宝贵建议和真诚评论。

 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/989550.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存