请选择 进入手机版 | 继续访问电脑版

[Golang] Go 协程超时控制的实现

[复制链接]
查看83 | 回复9 | 2021-9-15 01:09:25 | 显示全部楼层 |阅读模式
目次

Go 协程超时控制

  • Select 壅闭 方式
  • Context 方式

先说个场景:

假设业务中 A 服务必要 调用 服务B,要求设置 5s 超时,那么怎样 优雅实现?

Select 超时控制

思量 是否可以用 select + time.After 方式举行 实现

这里紧张 使用 的是通道在携程之间通讯 的特点,当程序调用成功后,会向通道中发送信号。没调用成功前,通道会壅闭 。

  1. select {
  2. case res := <-c2:
  3. fmt.Println(res)
  4. case <-time.After(time.Second * 3):
  5. fmt.Println("timeout 2")
  6. }
复制代码

当 c2 通道中有数据时,并且超时时间没有达到 3s,走 case res := <-c2 这个业务逻辑,当超时时间达到 3s , 走的 case <-time.After(time.Second * 3) 这个业务逻辑, 如许 就可以实现超时 3s 的控制。

res:= <-c2 是由于 channel 可以实现壅闭 ,那么 time.After 为啥可以壅闭 呢?

看 After 源码。sleep.go 可以看到实在 也是 channel

  1. func After(d Duration) <-chan Time {
  2. return NewTimer(d).C
  3. }
复制代码

完备 代码示例:

  1. package timeout
  2. import (
  3. "fmt"
  4. "testing"
  5. "time"
  6. )
  7. func TestSelectTimeOut(t *testing.T) {
  8. // 在这个例子中, 假设我们执行了一个外部调用, 2秒之后将结果写入c1
  9. c1 := make(chan string, 1)
  10. go func() {
  11. time.Sleep(time.Second * 2)
  12. c1 <- "result 1"
  13. }()
  14. // 这里使用select来实现超时, `res := <-c1`等待通道结果,
  15. // `<- Time.After`则在等待1秒后返回一个值, 因为select首先
  16. // 执行那些不再阻塞的case, 所以这里会执行超时程序, 如果
  17. // `res := <-c1`超过1秒没有执行的话
  18. select {
  19. case res := <-c1:
  20. fmt.Println(res)
  21. case <-time.After(time.Second * 1):
  22. fmt.Println("timeout 1")
  23. }
  24. // 如果我们将超时时间设为3秒, 这个时候`res := <-c2`将在
  25. // 超时case之前执行, 从而能够输出写入通道c2的值
  26. c2 := make(chan string, 1)
  27. go func() {
  28. time.Sleep(time.Second * 2)
  29. c2 <- "result 2"
  30. }()
  31. select {
  32. case res := <-c2:
  33. fmt.Println(res)
  34. case <-time.After(time.Second * 3):
  35. fmt.Println("timeout 2")
  36. }
  37. }
复制代码

运行效果 :

  1. === RUN   TestSelectTimeOut
  2. timeout 1
  3. result 2
  4. --- PASS: TestSelectTimeOut (3.00s)
  5. PASS
复制代码

go timer 计时器

这个是 timer 雷同 的计时器实现,通用也是通过通道来发送数据。

  1. package main
  2. import "time"
  3. import "fmt"
  4. func main() {
  5. // Ticker使用和Timer相似的机制, 同样是使用一个通道来发送数据。
  6. // 这里我们使用range函数来遍历通道数据, 这些数据每隔500毫秒被
  7. // 发送一次, 这样我们就可以接收到
  8. ticker := time.NewTicker(time.Millisecond * 500)
  9. go func() {
  10. for t := range ticker.C {
  11. fmt.Println("Tick at", t)
  12. }
  13. }()
  14. // Ticker和Timer一样可以被停止。 一旦Ticker停止后, 通道将不再
  15. // 接收数据, 这里我们将在1500毫秒之后停止
  16. time.Sleep(time.Millisecond * 1500)
  17. ticker.Stop()
  18. fmt.Println("Ticker stopped")
  19. }
复制代码

go context

context 监听是否有 IO 操作,开始从当前毗连 中读取网络哀求 ,每当读取到一个哀求 则会将该cancelCtx传入,用以传递取消信号,可发送取消信号,取消全部 举行 中的网络哀求 。

  1. go func(ctx context.Context, info *Info) {
  2. timeLimit := 120
  3. timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeLimit)*time.Millisecond)
  4. defer func() {
  5. cancel()
  6. wg.Done()
  7. }()
  8. resp := DoHttp(timeoutCtx, info.req)
  9. }(ctx, info)
复制代码

关键看业务代码: resp := DoHttp(timeoutCtx, info.req) 业务代码中包含 http 调用 NewRequestWithContext

  1. req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(paramString))
复制代码

上面的代码,设置了过期时间,当DoHttp(timeoutCtx, info.req) 处理时间超过超时时间时,会主动 制止 ,并且打印 context deadline exceeded。

看个代码:

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "testing"
  6. "time"
  7. )
  8. func TestTimerContext(t *testing.T) {
  9. now := time.Now()
  10. later, _ := time.ParseDuration("10s")
  11. ctx, cancel := context.WithDeadline(context.Background(), now.Add(later))
  12. defer cancel()
  13. go Monitor(ctx)
  14. time.Sleep(20 * time.Second)
  15. }
  16. func Monitor(ctx context.Context) {
  17. select {
  18. case <-ctx.Done():
  19. fmt.Println(ctx.Err())
  20. case <-time.After(20 * time.Second):
  21. fmt.Println("stop monitor")
  22. }
  23. }
复制代码

运行效果 :

  1. === RUN   TestTimerContext
  2. context deadline exceeded
  3. --- PASS: TestTimerContext (20.00s)
  4. PASS
复制代码

Context 接口有如下:

  1. type Context interface {
  2. Deadline() (deadline time.Time, ok bool)
  3. Done() <-chan struct{}
  4. Err() error
  5. Value(key interface{}) interface{}
  6. }
复制代码
  • Deadline — 返回 context.Context 被取消的时间,也就是完成工作的制止 日期;
  • Done — 返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel;
  • Err — 返回 context.Context 竣事 的缘故原由 ,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值;
    • 假如 context.Context 被取消,会返回 Canceled 错误;
    • 假如 context.Context 超时,会返回 DeadlineExceeded 错误;
  • Value — 从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入雷同 的 Key 会返回雷同 的效果 ,该方法可以用来传递哀求 特定的数据;

到此这篇关于Go 协程超时控制的实现的文章就先容 到这了,更多干系 Go 协程超时控制内容请搜索 脚本之家从前 的文章或继续欣赏 下面的干系 文章渴望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar Jacqueline季 | 2021-9-26 20:56:40 | 显示全部楼层
禽兽不如应该不是说admin楼主的的吧?
回复

使用道具 举报

avatar 123457684 | 2021-10-5 03:59:20 | 显示全部楼层
我回帖admin楼主给加积分吗?
回复

使用道具 举报

avatar 无缘人1 | 2021-10-6 05:03:42 | 显示全部楼层
感觉不错!
回复

使用道具 举报

收藏了,以后可能会用到!
回复

使用道具 举报

看帖不回帖的人就是耍流氓,我回复了!
回复

使用道具 举报

哥回复的不是帖子,是寂寞!
回复

使用道具 举报

最近压力山大啊!
回复

使用道具 举报

吹牛的人越来越多了!
回复

使用道具 举报

小弟默默的路过贵宝地~~~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则