系列文章目录
目录
1.使用Context CancelFunc限制线程启动次数
前言
前面的文章主要介绍了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的使用方法和使用场景。欢迎大家对我的博客提出宝贵建议和真诚评论。