原文链接:https://www.zhoubotong.site/post/57.html

golang的超时处理

2天前Go实例技巧25

 

大家知道Select 是 Go 中的一个控制结构,每个 case 必须是一个通信操作,要么是发送要么是接收操作。 select是 随机执行一个可运行的 case。

如果没有 case 可运行,程序可能会阻塞,直到有 case 可运行。当然有一个默认的子句(default子句)在没有任何条件满足的时候总是可运行的。

对于处理资源密集型的应用程序,超时处理是不可避免的。检查超时是有必要的,以确保超时运行的任务不会消耗应用程序的其他服务组件可能需要的资源或网络带宽。
Golang处理超时的方法非常简单。不需要复杂的代码,我们可以用channel通信和使用select语句作出超时决策来处理超时的问题。

在Go中,Select主要是和channel有关,其大概的格式如下:

select{
case <- ch1: // 读取ch1
// 执行操作
case i := <- ch2 // 读取ch2
// 使用i 执行操作
default:
//
}  

Go的select与channel配合使用进行超时处理。channel必须是有缓冲channel,不然就是同步操作了。
select用于等待一个或者多个channel的输出。

应用场景

主goroutine等待子goroutine完成,但是子goroutine无限运行,导致主goroutine会一直等待下去(注意main也是一个携程)。而主线程想超过了一定的时间如果没有返回的话,

这时候可以进行超时判断然后继续运行下去。

package main

import (
"fmt"
"time"
) func main() {
chn := make(chan bool, 1)
// 并发执行一个函数,等待3s后向chn写入true
go func() {
time.Sleep(3 * time.Second)
chn <- true
}() /*
这里会等待chn或timeout读出数据
因为一直没有向chn写入数据
在3s后向chn写入了数据
所以执行了timeout的case
利用这个技巧可以实现超时操作
*/
select {
case chn1 := <-chn:
fmt.Println(chn1)
return
case <-time.After(4 * time.Second): //超时判断(程序执行4s后,因为3s内chn已经发送了true,所以输出 true)
fmt.Println("超时timeout")
//如果将time.After中改为1*time.Second,则输出为:
return
} } 

我再举个开发中经常用到的例子,比如模拟网络连接,我们从一个模拟get请求的服务中读取响应。

如下面我编写一个简单结构体来接收服务的响应内容(这个例子没有考虑超时问题,稍后我后面说明补上)。

type Result struct {
UserID int `json:"user_id"`
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}

这里我直截了当地写了一个快速方法来获取服务中的响应,并返回给客户端,完整代码如下:

package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
) type Result struct {
UserID int `json:"user_id"`
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
} // 发送http get请求获取相应数据
func GetHttpResult(URL string) (*Result, error) {
resp, err := http.Get(URL) if err != nil {
return nil, fmt.Errorf(err.Error())
} defer resp.Body.Close()
byteResp, err := ioutil.ReadAll(resp.Body) if err != nil {
return nil, fmt.Errorf(err.Error())
} structResp := &Result{}
err = json.Unmarshal(byteResp, structResp) // 解析json数据到Result结构体指向的值 if err != nil {
return nil, fmt.Errorf("error in unmarshalling Result")
} return structResp, nil
}
func main() { res, err := GetHttpResult("https://jsonplaceholder.typicode.com/todos/1") // 正常该请求毫秒回 if err != nil {
fmt.Printf("err %v", err)
} else {
fmt.Printf("res %v", res)
}
} 

这是非常简单的方法。只是使用Golang原生http库读取http调用中的信息,并将响应内容存放在结构体中,在不同的步骤中处理错误。非常简单!

结果输出了一个来自模拟服务的虚拟响应信息如下(未超时):

res &{0 1 delectus aut autem false} 

现在来看请求正常,假设连接需要很长时间才能从服务器中获得响应,那么main函数将等待不确定时间了。

在实际应用程序中,这是没法接受的,因为这会消耗很多资源。要解决这个问题,我们在GetHttpResult函数中添加一个context参数。

func GetHttpResult(ctx context.Context) (*Result, error) 

这个context可以告我们何时停止尝试从网络中获取的结果。为了验证这一点,先编写一个帮助函数,执行和前面相同的操作,返回结果并将结果写入channel,

并使用一个独立的goroutine来执行实际的工作。为了简单起见,可以将响应和错误包装在一个CallResult结构体中,完整代码如下:

package main

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
) // 定义响应结构体
type Result struct {
UserID int `json:"user_id"`
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
} type CallResult struct {
Resp *Result
Err error
} func helper(ctx context.Context) <-chan *CallResult { respChan := make(chan *CallResult, 1) go func() {
resp, err := http.Get("https://jsonplaceholder.typicode.com/todos/1")
time.Sleep(2000 * time.Millisecond) // 模拟超时请求 Millisecond表示1毫秒的时间间隔
//比如睡眠1小时10分5秒:time.Sleep(1*time.Hour + 10*time.Minute + 5*time.Second) if err != nil {
respChan <- &CallResult{nil, fmt.Errorf(err.Error())}
return
} defer resp.Body.Close()
byteResp, err := ioutil.ReadAll(resp.Body) if err != nil {
respChan <- &CallResult{nil, fmt.Errorf(err.Error())}
return
} structResp := &Result{}
err = json.Unmarshal(byteResp, structResp) if err != nil {
respChan <- &CallResult{nil, fmt.Errorf("error in unmarshalling Result")}
} respChan <- &CallResult{structResp, nil}
}() return respChan
} func GetHttpResult(ctx context.Context) (*Result, error) {
select {
case <-ctx.Done():
return nil, fmt.Errorf("context timeout, ran out of time")
case respChan := <-helper(ctx):
return respChan.Resp, respChan.Err }
} func main() { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) // 超过1s响应标记为超时
defer cancel()
res, err := GetHttpResult(ctx) if err != nil {
fmt.Printf("err %v", err)
} else {
fmt.Printf("res %v", res)
} }

运行上面代码可以得到和之前相同的响应信息(注释掉time.Sleep)正常输出:

res &{0 1 delectus aut autem false} 

上面所示代码中,创建了一个带缓存的respChan通道。然后执行和GetHttpResult函数相同的工作,但是用一个CallResult来代替返回响应和错误。

函数结束返回resChan。然后在单独的goroutine中执行网络连接,并将结果写入channel。这样代码实现非阻塞。

可以看到GetHttpResult函数现在变的更简单了,因为它必须做一个简单的选择。要么从通道中读取响应要么超时退出。

上面实现超时策略是通过select语句来完成的。以下是Done函数的定义:

Bash
Done() <-chan struct{} 

Done返回一个channel,当涉及的context被取消,channel就会关闭。当context中有超时,就会在超时的时候对通道进行写操作。

在这种情况下,代码返回一个表示超时的错误响应信息。
另一个case是,helper函数能够在超时之前完成服务的响应读取,并写入channel。在这种情况下,在respChan变量中得到结果并返回给客户端。
上面main函数中调用GetHttpResult并传入一个1秒超时的context参数。再将超时减少到1毫秒(

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) 

),因为1毫秒内不足以完成网络调用。因此不会过多占用任何资源,而且只要context超时,就向客户端返回错误,而不是一直等待响应了。

golang的超时处理使用技巧的更多相关文章

  1. golang chan 超时

    golang chan 超时 Posted on 2013-12-24 13:03 oathleo 阅读(4227) 评论(0)  编辑  收藏 package main import (    &q ...

  2. Golang fmt包使用小技巧

    h1 { margin-top: 0.6cm; margin-bottom: 0.58cm; direction: ltr; color: #000000; line-height: 200%; te ...

  3. Golang的防坑小技巧

    Golang的防坑小技巧 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 作为一名小白,在之前没有接触到编程的小伙伴,难免会踩到一些坑,比如说刚刚入门的时候你需要安装环境,学习Gol ...

  4. golang实现参数可变的技巧

    Go 使用默认参数的技巧 Functional Options Pattern in Go golang中没有函数默认参数的设计,因此需要些特别的技巧来实现. 假如我们需要订购一批电脑,其中电脑配置c ...

  5. golang网络通信超时设置

    网络通信中,为了防止长时间无响应的情况,经常会用到网络连接超时.读写超时的设置. 本文结合例子简介golang的连接超时和读写超时设置. 1.超时设置 1.1 连接超时 func DialTimeou ...

  6. [Golang]-6 超时处理、非阻塞通道操作、通道的关闭和遍历

    目录 超时处理 非阻塞通道操作 通道的关闭 通道遍历 超时处理 超时 对于一个连接外部资源,或者其它一些需要花费执行时间的操作的程序而言是很重要的. 得益于通道和 select,在 Go中实现超时操作 ...

  7. golang RPC通信读写超时设置

    golang RPC通信中,有时候就怕读写hang住. 那是否可以设置读写超时呢? 1.方案一: 设置连接的读写超时 1.1 client RPC通信基于底层网络通信,可以通过设置connection ...

  8. Golang os/exec 实现

    os/exec 实现了golang调用shell或者其他OS中已存在的命令的方法. 本文主要是阅读内部实现后的一些总结. 如果要运行ls -rlt,代码如下: package main import ...

  9. UVA-11214 Guarding the Chessboard (迭代加深搜索)

    题目大意:在一个国际象棋盘上放置皇后,使得目标全部被占领,求最少的皇后个数. 题目分析:迭代加深搜索,否则超时. 小技巧:用vis[0][r].vis[1][c].vis[2][r+c].vis[c- ...

随机推荐

  1. SMTP协议解读以及如何使用SMTP协议发送电子邮件

    电子邮件协议中POP3协议用于接收邮件,SMTP协议用于发送邮件.SMTP的全称为Simple Mail Transfer Protocol,也就是简单邮件传输协议,字如其名.   相较于POP3而言 ...

  2. 基于.NetCore开发博客项目 StarBlog - (3) 模型设计

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  3. IDEA新建项目时的默认配置与模版配置

    今天一大早,群里(点击加群)有小伙伴问了这样的一个问题: 在我们使用IDEA开发项目的时候,通常都会有很多配置项需要去设置,比如对于Java项目来说,一般就包含:JDK配置.Maven配置等.那么如果 ...

  4. 漫谈 HTTP 连接

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 本文首先会 HTTP 的特点和优缺点,然后会详细介绍 HTTP 长连接和短连接的连接管理,通过 ...

  5. python3修改HTMLTestRunner,生成有截图的测试报告,并发送测试邮件(二)

    3. 如何将第一步得到的地址和名称 输入 进第二步里的表格中呢... 用上述查找元素的方法,发现HTMLTestRunner.py中REPORT_TEST_WITH_OUTPUT_TMPL是用来输出测 ...

  6. 腾讯tbs 内存泄露

    一.背景 TBS(腾讯浏览服务)是腾讯提供的移动端webview体验的整套解决方案(https://x5.tencent.com/docs/index.html),可以用于移动端加载doc.xls.p ...

  7. scrapy架构与目录介绍、scrapy解析数据、配置相关、全站爬取cnblogs数据、存储数据、爬虫中间件、加代理、加header、集成selenium

    今日内容概要 scrapy架构和目录介绍 scrapy解析数据 setting中相关配置 全站爬取cnblgos文章 存储数据 爬虫中间件和下载中间件 加代理,加header,集成selenium 内 ...

  8. 0.1+0.2不等于0.3,微信小程序云开发如何解决JavaScript小数计算精度失准的问题

    先看图 这个是JavaScript语言自身存在的一个问题.说道这里不得不提一下网上流传的JavaScript搞笑图 我们在使用云开发来开发微信小程序的时候,会经常遇到JavaScript小数计算精度失 ...

  9. extcon驱动及其在USB驱动中的应用

    extcon,是External Connector的简称,用于抽象外部连接器,比如说Audio Jack.USB MicroB/TypeC接口等.它的原型是Android的switch-class驱 ...

  10. 821. Shortest Distance to a Character - LeetCode

    Question 821. Shortest Distance to a Character Solution 思路:遍历字符串S,遇到与字符C相等就分别向左/右计算其他字符与该字符的距离,如果其他字 ...