Go语言实现超时的三种方法实例

目录
  • 前言
  • 方法一:用两个通道 + A协程sleep
  • 方法二:使用Timer(定时器)
  • 方法三:使用context.WithTimeout
  • 附:go 实现超时退出
  • 总结

前言

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

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

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

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

package main

import (
	"fmt"
	"time"
)

// 老师视频里的生产者消费者

func main() {
	//知识点: 老师这里用了两个线程,一个用个传数据,一个用来传关闭信号
	messages := make(chan int, 10)
	done := make(chan bool)

	defer close(messages)

	// consumer
	go func() {
		ticker := time.NewTicker(1 * time.Second)
		for range ticker.C {
			select {
			case <-done:
				fmt.Println("child process interrupt...") // 数据还没收完,就被停止了。
				return
			default:
				fmt.Printf("receive message:%d\n", <-messages)
			}

		}
	}()

	// producer
	for i := 0; i < 10; i++ {
		messages <- i
	}

	// 5秒后主线程关闭done通道
	time.Sleep(5 * time.Second)
	close(done)
	time.Sleep(1 * time.Second)
	fmt.Println("main process exit!")
}

程序输出如下:

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

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

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

package main

import (
	"fmt"
	"time"
)

//知识点:
// 1) 多通道
// 2) 定时器
func main() {
	ch1 := make(chan int, 10)
	go func(ch chan<- int) {
		// 假设子协程j是一个耗时操作,例如访问网络,要10秒后才会有数据
		time.Sleep(10 * time.Second)
		ch <- 1
	}(ch1)

	timer := time.NewTimer(5 * time.Second) // 设置定时器的超时时间,主线程只等5秒

	fmt.Println("select start....")
	// 知识点:主协程等待子线程,并有超时机制
	select {
	case <-ch1:
		fmt.Println("从channel 1 收到一个数字")
	case <-timer.C: // 定时器也是一个通道
		fmt.Println("5秒到了,超时了,main协程不等了")
	}

	fmt.Println("done!")
}

程序输出如下:

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行。

package main

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

// 课后练习 1.2
// 基于 Channel 编写一个简单的单协程生产者消费者模型。
// 要求如下:
// 1)队列:队列长度 10,队列元素类型为 int
// 2)生产者:每 1 秒往队列中放入一个类型为 int 的元素,队列满时生产者可以阻塞
// 3)消费者:每2秒从队列中获取一个元素并打印,队列为空时消费者阻塞
// 4)主协程30秒后要求所有子协程退出。
// 5)要求优雅退出,即消费者协程退出前,要先消费完所有的int。

// 知识点:
// 1) 切片的零值也是可用的。
// 2) context.WithTimeout
var (
	wg sync.WaitGroup
	p  Producer
	c  Consumer
)

type Producer struct {
	Time     int
	Interval int
}

type Consumer struct {
	Producer
}

func (p Producer) produce(queue chan<- int, ctx context.Context) {
	go func() {
	LOOP:
		for {
			p.Time = p.Time + 1
			queue <- p.Time
			fmt.Printf("生产者进行第%d次生产,值:%d\n", p.Time, p.Time)
			time.Sleep(time.Duration(p.Interval) * time.Second)

			select {
			case <-ctx.Done():
				close(queue)
				break LOOP
			}
		}
		wg.Done()
	}()
}

func (c Consumer) consume(queue <-chan int, ctx context.Context) {
	go func() {
	LOOP:
		for {
			c.Time++
			val := <-queue
			fmt.Printf("-->消费者进行第%d次消费,值:%d\n", c.Time, val)
			time.Sleep(time.Duration(c.Interval) * time.Second)

			select {
			case <-ctx.Done():
				//remains := new([]int)
				//remains := []int{}
				var remains []int // 知识点:切片的零值也是可用的。
				for val = range queue {
					remains = append(remains, val)
					fmt.Printf("-->消费者: 最后一次消费, 值为:%v\n", remains)
					break LOOP
				}
			}
		}
		wg.Done()
	}()
}

func main() {
	wg.Add(2)

	// 知识点:context.Timeout
	timeout := 30
	ctx, _ := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)

	queue := make(chan int, 10)

	p.produce(queue, ctx)
	fmt.Println("main waiting...")
	wg.Wait()
	fmt.Println("done")
}

/*
启动命令:
$ go run main/main.go -m wb
$ go run main/main.go -m je
*/
func init() {
	// 解析程序入参,运行模式
	mode := flag.String("m", "wb", "请输入运行模式:\nwb(温饱模式)生产速度快过消费速度、\nje(饥饿模式)生产速度慢于消费速度)")
	flag.Parse()

	p = Producer{}
	c = Consumer{}

	if *mode == "wb" {
		fmt.Println("运行模式:wb(温饱模式)生产速度快过消费速度")
		p.Interval = 1 // 每隔1秒生产一次
		c.Interval = 5 // 每隔5秒消费一次

		// p = Producer{Interval: 1}
		// c = Consumer{Interval: 5}  // 这一行会报错,为什么?

	} else {
		fmt.Println("运行模式:je(饥饿模式)生产速度慢于消费速度")
		p.Interval = 5 // 每隔5秒生产一次
		c.Interval = 1 // 每隔1秒消费一次
	}
}

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

func AsyncCall() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
	defer cancel()
	go func(ctx context.Context) {
		// 发送HTTP请求
	}()

	select {
	case <-ctx.Done():
		fmt.Println("call successfully!!!")
		return
	case <-time.After(time.Duration(time.Millisecond * 900)):
		fmt.Println("timeout!!!")
		return
	}
}

//2
func AsyncCall() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond * 800))
	defer cancel()
	timer := time.NewTimer(time.Duration(time.Millisecond * 900))

	go func(ctx context.Context) {
		// 发送HTTP请求
	}()

	select {
	case <-ctx.Done():
		timer.Stop()
		timer.Reset(time.Second)
		fmt.Println("call successfully!!!")
		return
	case <-timer.C:
		fmt.Println("timeout!!!")
		return
	}
}

//3
func AsyncCall() {
  ctx := context.Background()
	done := make(chan struct{}, 1)

	go func(ctx context.Context) {
		// 发送HTTP请求
		done <- struct{}{}
	}()

	select {
	case <-done:
		fmt.Println("call successfully!!!")
		return
	case <-time.After(time.Duration(800 * time.Millisecond)):
		fmt.Println("timeout!!!")
		return
	}
}

总结

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

(0)

相关推荐

  • 详解Golang 中的并发限制与超时控制

    前言 上回在 用 Go 写一个轻量级的 ssh 批量操作工具里提及过,我们做 Golang 并发的时候要对并发进行限制,对 goroutine 的执行要有超时控制.那会没有细说,这里展开讨论一下. 以下示例代码全部可以直接在 The Go Playground上运行测试: 并发 我们先来跑一个简单的并发看看 package main import ( "fmt" "time" ) func run(task_id, sleeptime int, ch chan st

  • Go并发调用的超时处理的方法

    之前有聊过 golang 的协程,我发觉似乎还很理论,特别是在并发安全上,所以特结合网上的一些例子,来试验下go routine中 的 channel, select, context 的妙用. 场景-微服务调用 我们用 gin(一个web框架) 作为处理请求的工具,需求是这样的: 一个请求 X 会去并行调用 A, B, C 三个方法,并把三个方法返回的结果加起来作为 X 请求的 Response. 但是我们这个 Response 是有时间要求的(不能超过3秒的响应时间),可能 A, B, C

  • Golang实现超时退出的三种方式

    前段时间发现线上有个服务接口,总是间歇性告警,有时候一天两三次,有时候一天都没有. 告警的逻辑是在一个接口中异步调用了另一个HTTP接口,这个HTTP接口调用出现超时.但是我去问了负责这个HTTP接口的同学,人家说他们的接口相应都是毫秒级别,还截图监控了,有图有真相,我还能说啥. 但是,超时是确实存在的,只是请求还可能没有到人家服务那边. 这种偶发性问题不好复现,偶尔来个告警也挺烦的,第一反应还是先解决问题,思路也简单,失败后重试. 解决方法 且不谈重试策略,先说说什么时候触发重试. 我们可以在

  • Go语言如何并发超时处理详解

    实现原理: 并发一个函数,等待1s后向timeout写入数据,在select中如果1s之内有数据向其他channel写入则会顺利执行,如果没有,这是timeout写入了数据,则我们知道超时了. 实现代码: package main import "fmt" import "time" func main() { ch := make(chan int, 1) timeout := make(chan bool, 1) // 并发执行一个函数,等待1s后向timeou

  • Go语言实现超时的三种方法实例

    目录 前言 方法一:用两个通道 + A协程sleep 方法二:使用Timer(定时器) 方法三:使用context.WithTimeout 附:go 实现超时退出 总结 前言 超时,指一个协程A开启另一个协程B,A会阻塞等待B一段指定的时间,例如:5秒,A通知B结束(也有可能不通知,让B继续运行).也就是说,A就不愿意阻塞等待太久. Go语言有多种方法实现这种超时,我总结出3种: 方法一:用两个通道 + A协程sleep 一个通道用来传数据,一个用来传停止信号. package main imp

  • JS创建事件的三种方法(实例代码)

    1.普通的定义方式 <input type="button" name="Button" value="确定" onclick="Sfont=prompt('请在文本框中输入红色','红色',' 提示框 '); if(Sfont=='红色'){ form1.style.fontFamily='黑体'; form1.style.color='red'; }" /> 这是最常见的一种定义方式,直接将JS事件定义在需要的

  • JavaScript复制变量三种方法实例详解

    这篇文章主要介绍了JavaScript复制变量三种方法实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 直接将一个变量赋给另一个变量时,系统并不会创造一个新的变量,而是将原变量的地址赋给了新变量名.举个栗子: 复制代码 复制代码 let obj = { a: 1, b: 2, }; let copy = obj; obj.a = 5; console.log(copy.a); // Result // a = 5; // 更改obj的值,

  • Java追加文件内容的三种方法实例代码

    整理文档,搜刮出一个Java追加文件内容的三种方法的代码,稍微整理精简一下做下分享. import Java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.RandomAccessFile;

  • PHP去除字符串最后一个字符的三种方法实例

    前言 本文讲讲PHP中如何正确的去除字符串中的最后一个字符,之前跟大家分享过一篇关于PHP去除字符串最后一个字符的三种方法的文章,但是没给出实例,下面话不多说,直接上代码,相信一眼就能看出来了,直接将下面代码复制粘贴到自己本地服务器下,运行即可. 实例代码 $a = 'http://www.mafutian.net/'; $b = 'http://www.mafutian.net'; // 错误的方式: $len = strlen($a) - 1; $a{$len} = ''; // $a[$l

  • php获取POST数据的三种方法实例详解

    php获取POST数据的三种方法 方法一,$_POST $_POST或$_REQUEST存放的是PHP以key=>value的形式格式化以后的数据. 方法二,使用file_get_contents("php://input") 对于未指定 Content-Type 的POST数据,则可以使用file_get_contents("php://input");来获取原始数据. 事实上,用PHP接收POST的任何数据均使用本方法.而不用考虑Content-Type,

  • jquery动态加载js三种方法实例

    复制代码 代码如下: <!doctype html public "-//w3c//dtd xhtml 1.0 transitional//en" "http://www.w3.org/tr/xhtml1/dtd/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="

  • PHP获取对象属性的三种方法实例分析

    本文实例讲述了PHP获取对象属性的三种方法.分享给大家供大家参考,具体如下: 今天查看yii源码,发现yii\base\Model中的attribute()方法是通过反射获取对象的public non-static属性.记得以前看到的代码都是用get_object_vars()这个函数获取的,昨天查看php文档,发现还可以用foreach遍历对象属性.于是写个例子练习下. class TestClass { private $a; protected $b; public $c; public

  • jQuery中each遍历的三种方法实例分析

    本文实例讲述了jQuery中each遍历的三种方法.分享给大家供大家参考,具体如下: 1.选择器+遍历 $('div').each(function (i){ //i就是索引值 //this 表示获取遍历每一个dom对象 }); 2.选择器+遍历 $('div').each(function (index,domEle){ //index就是索引值 //domEle 表示获取遍历每一个dom对象 }); 3.更适用的遍历方法 1)先获取某个集合对象 2)遍历集合对象的每一个元素 var d=$(

  • node.js express捕获全局异常的三种方法实例分析

    本文实例讲述了node.js express捕获全局异常的三种方法.分享给大家供大家参考,具体如下: 场景 express的路由里抛出异常后,全局中间件没办法捕获,需要在所有的路由函数里写try catch,这坑爹的逻辑让人每次都要多写n行代码 官方错误捕获中件间代码如下 app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); }); 测

随机推荐