ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

golang的超时处理使用技巧

2022-07-06 10:33:31  阅读:203  来源: 互联网

标签:技巧 err golang json Result context 超时 channel


原文链接: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超时,就向客户端返回错误,而不是一直等待响应了。

标签:技巧,err,golang,json,Result,context,超时,channel
来源: https://www.cnblogs.com/phpper/p/16449764.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有