经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
Go语言实现超时的三种方法实例
来源:jb51  时间:2022/7/20 13:09:13  对本文有异议

前言

超时,指一个协程A开启另一个协程B,A会阻塞等待B一段指定的时间,例如:5秒,A通知B结束(也有可能不通知,让B继续运行)。也就是说,A就不愿意阻塞等待太久。

Go语言有多种方法实现这种超时,我总结出3种:

方法一:用两个通道 + A协程sleep

一个通道用来传数据,一个用来传停止信号。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. // 老师视频里的生产者消费者
  7. func main() {
  8. //知识点: 老师这里用了两个线程,一个用个传数据,一个用来传关闭信号
  9. messages := make(chan int, 10)
  10. done := make(chan bool)
  11. defer close(messages)
  12. // consumer
  13. go func() {
  14. ticker := time.NewTicker(1 * time.Second)
  15. for range ticker.C {
  16. select {
  17. case <-done:
  18. fmt.Println("child process interrupt...") // 数据还没收完,就被停止了。
  19. return
  20. default:
  21. fmt.Printf("receive message:%d\n", <-messages)
  22. }
  23. }
  24. }()
  25. // producer
  26. for i := 0; i < 10; i++ {
  27. messages <- i
  28. }
  29. // 5秒后主线程关闭done通道
  30. time.Sleep(5 * time.Second)
  31. close(done)
  32. time.Sleep(1 * time.Second)
  33. fmt.Println("main process exit!")
  34. }

程序输出如下:

receive message:0
receive message:1
receive message:2
receive message:3
child process interrupt...
main process exit!

方法二:使用Timer(定时器)

这种方法也方法一类似,只不过是用一个Timer代替通道。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. //知识点:
  7. // 1) 多通道
  8. // 2) 定时器
  9. func main() {
  10. ch1 := make(chan int, 10)
  11. go func(ch chan<- int) {
  12. // 假设子协程j是一个耗时操作,例如访问网络,要10秒后才会有数据
  13. time.Sleep(10 * time.Second)
  14. ch <- 1
  15. }(ch1)
  16. timer := time.NewTimer(5 * time.Second) // 设置定时器的超时时间,主线程只等5秒
  17. fmt.Println("select start....")
  18. // 知识点:主协程等待子线程,并有超时机制
  19. select {
  20. case <-ch1:
  21. fmt.Println("从channel 1 收到一个数字")
  22. case <-timer.C: // 定时器也是一个通道
  23. fmt.Println("5秒到了,超时了,main协程不等了")
  24. }
  25. fmt.Println("done!")
  26. }

程序输出如下:

select start....
5秒到了,超时了,main协程不等了
done!

方法三:使用context.WithTimeout

下面的例子比较复杂,基于 Channel 编写一个简单的单协程生产者消费者模型。

要求如下:

1)队列:队列长度 10,队列元素类型为 int

2)生产者:每 1 秒往队列中放入一个类型为 int 的元素,队列满时生产者可以阻塞

3)消费者:每2秒从队列中获取一个元素并打印,队列为空时消费者阻塞

4)主协程30秒后要求所有子协程退出。

5)要求优雅退出,即消费者协程退出前,要先消费完所有的int

6)通过入参支持两种运行模式:

  • wb(温饱模式)生产速度快过消费速度、
  • je(饥饿模式)生产速度慢于消费速度

context.WithTimeout见第87行。

  1. package main
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "sync"
  7. "time"
  8. )
  9. // 课后练习 1.2
  10. // 基于 Channel 编写一个简单的单协程生产者消费者模型。
  11. // 要求如下:
  12. // 1)队列:队列长度 10,队列元素类型为 int
  13. // 2)生产者:每 1 秒往队列中放入一个类型为 int 的元素,队列满时生产者可以阻塞
  14. // 3)消费者:每2秒从队列中获取一个元素并打印,队列为空时消费者阻塞
  15. // 4)主协程30秒后要求所有子协程退出。
  16. // 5)要求优雅退出,即消费者协程退出前,要先消费完所有的int。
  17. // 知识点:
  18. // 1) 切片的零值也是可用的。
  19. // 2) context.WithTimeout
  20. var (
  21. wg sync.WaitGroup
  22. p Producer
  23. c Consumer
  24. )
  25. type Producer struct {
  26. Time int
  27. Interval int
  28. }
  29. type Consumer struct {
  30. Producer
  31. }
  32. func (p Producer) produce(queue chan<- int, ctx context.Context) {
  33. go func() {
  34. LOOP:
  35. for {
  36. p.Time = p.Time + 1
  37. queue <- p.Time
  38. fmt.Printf("生产者进行第%d次生产,值:%d\n", p.Time, p.Time)
  39. time.Sleep(time.Duration(p.Interval) * time.Second)
  40. select {
  41. case <-ctx.Done():
  42. close(queue)
  43. break LOOP
  44. }
  45. }
  46. wg.Done()
  47. }()
  48. }
  49. func (c Consumer) consume(queue <-chan int, ctx context.Context) {
  50. go func() {
  51. LOOP:
  52. for {
  53. c.Time++
  54. val := <-queue
  55. fmt.Printf("-->消费者进行第%d次消费,值:%d\n", c.Time, val)
  56. time.Sleep(time.Duration(c.Interval) * time.Second)
  57. select {
  58. case <-ctx.Done():
  59. //remains := new([]int)
  60. //remains := []int{}
  61. var remains []int // 知识点:切片的零值也是可用的。
  62. for val = range queue {
  63. remains = append(remains, val)
  64. fmt.Printf("-->消费者: 最后一次消费, 值为:%v\n", remains)
  65. break LOOP
  66. }
  67. }
  68. }
  69. wg.Done()
  70. }()
  71. }
  72. func main() {
  73. wg.Add(2)
  74. // 知识点:context.Timeout
  75. timeout := 30
  76. ctx, _ := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
  77. queue := make(chan int, 10)
  78. p.produce(queue, ctx)
  79. fmt.Println("main waiting...")
  80. wg.Wait()
  81. fmt.Println("done")
  82. }
  83. /*
  84. 启动命令:
  85. $ go run main/main.go -m wb
  86. $ go run main/main.go -m je
  87. */
  88. func init() {
  89. // 解析程序入参,运行模式
  90. mode := flag.String("m", "wb", "请输入运行模式:\nwb(温饱模式)生产速度快过消费速度、\nje(饥饿模式)生产速度慢于消费速度)")
  91. flag.Parse()
  92. p = Producer{}
  93. c = Consumer{}
  94. if *mode == "wb" {
  95. fmt.Println("运行模式:wb(温饱模式)生产速度快过消费速度")
  96. p.Interval = 1 // 每隔1秒生产一次
  97. c.Interval = 5 // 每隔5秒消费一次
  98. // p = Producer{Interval: 1}
  99. // c = Consumer{Interval: 5} // 这一行会报错,为什么?
  100. } else {
  101. fmt.Println("运行模式:je(饥饿模式)生产速度慢于消费速度")
  102. p.Interval = 5 // 每隔5秒生产一次
  103. c.Interval = 1 // 每隔1秒消费一次
  104. }
  105. }

wb(温饱模式)生产速度快过消费速度,输出如下:

运行模式:wb(温饱模式)生产速度快过消费速度
生产者: 第1次生产, 值为:1
-->消费者: 第1次消费, 值为:1
生产者: 第2次生产, 值为:2
生产者: 第3次生产, 值为:3
生产者: 第4次生产, 值为:4
生产者: 第5次生产, 值为:5
-->消费者: 第2次消费, 值为:2
生产者: 第6次生产, 值为:6
生产者: 第7次生产, 值为:7
生产者: 第8次生产, 值为:8
生产者: 第9次生产, 值为:9
生产者: 第10次生产, 值为:10
-->消费者: 第3次消费, 值为:3
生产者: 第11次生产, 值为:11
生产者: 第12次生产, 值为:12
生产者: 第13次生产, 值为:13
-->消费者: 第4次消费, 值为:4
生产者: 第14次生产, 值为:14
-->消费者: 第5次消费, 值为:5
生产者: 第15次生产, 值为:15
生产者: 第16次生产, 值为:16
-->消费者: 第6次消费, 值为:6
main waiting
生产者: 第17次生产, 值为:17
-->消费者: 最后一次消费, 值为:[7 8 9 10 11 12 13 14 15 16 17]
-- done --

je(饥饿模式)生产速度慢于消费速度,输出如下:

运行模式:je(饥饿模式)生产速度慢于消费速度
-->消费者: 第1次消费, 值为:1
生产者: 第1次生产, 值为:1
生产者: 第2次生产, 值为:2
-->消费者: 第2次消费, 值为:2
生产者: 第3次生产, 值为:3
-->消费者: 第3次消费, 值为:3
生产者: 第4次生产, 值为:4
-->消费者: 第4次消费, 值为:4
生产者: 第5次生产, 值为:5
-->消费者: 第5次消费, 值为:5
生产者: 第6次生产, 值为:6
-->消费者: 第6次消费, 值为:6
main waiting
-->消费者: 第7次消费, 值为:0

附:go 实现超时退出

之前手写rpc框架的时候,吃多了网络超时处理的苦,今天偶然发现了实现超时退出的方法,MARK

  1. func AsyncCall() {
  2. ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
  3. defer cancel()
  4. go func(ctx context.Context) {
  5. // 发送HTTP请求
  6. }()
  7.  
  8. select {
  9. case <-ctx.Done():
  10. fmt.Println("call successfully!!!")
  11. return
  12. case <-time.After(time.Duration(time.Millisecond * 900)):
  13. fmt.Println("timeout!!!")
  14. return
  15. }
  16. }
  17.  
  18. //2
  19. func AsyncCall() {
  20. ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond * 800))
  21. defer cancel()
  22. timer := time.NewTimer(time.Duration(time.Millisecond * 900))
  23.  
  24. go func(ctx context.Context) {
  25. // 发送HTTP请求
  26. }()
  27.  
  28. select {
  29. case <-ctx.Done():
  30. timer.Stop()
  31. timer.Reset(time.Second)
  32. fmt.Println("call successfully!!!")
  33. return
  34. case <-timer.C:
  35. fmt.Println("timeout!!!")
  36. return
  37. }
  38. }
  39.  
  40.  
  41. //3
  42. func AsyncCall() {
  43. ctx := context.Background()
  44. done := make(chan struct{}, 1)
  45.  
  46. go func(ctx context.Context) {
  47. // 发送HTTP请求
  48. done <- struct{}{}
  49. }()
  50.  
  51. select {
  52. case <-done:
  53. fmt.Println("call successfully!!!")
  54. return
  55. case <-time.After(time.Duration(800 * time.Millisecond)):
  56. fmt.Println("timeout!!!")
  57. return
  58. }
  59. }

总结

到此这篇关于Go语言实现超时的三种方法的文章就介绍到这了,更多相关Go语言实现超时方法内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号