2019年8月

超时是一种常见的并发模式。您希望等待一个长时间运行的任务,但不希望永远等待。有几种方法可以在Go中实现超时,有些方法比其他方法更容易管理。我将概述其中的三种方法(尽管我不建议使用第一个方法),如果您想跳过,我更喜欢第三种方法。

方法一:快而不优雅

第一个方法是我认为大多数人会首先尝试的方法,因为它使用了许多语言中常见的概念,而且它在谷歌搜索“golang timeout”时排名很高,这是2010年的一篇博客文章中概述的。使用time . sleep:

ch := make(chan bool, 1)
timeout := make(chan bool, 1)

// 1s后,往timeout的chan中发送bool值true
go func() {
  time.Sleep(1 * time.Second)
  timeout <- true
}()

// 要么1s拿到ch结果,要么当1s时拿到timeout chan结果(这时候超时)
select {
case <-ch:
  fmt.Println("Read from ch")
case <-timeout:
  fmt.Println("Timed out")
}

这个示例将等待,直到它从ch或超时通道接收到一些内容。因为我们从来没有发送给ch,它总是在1s之后超时。好又简单。然而,事后很难清理干净。如果我们不超时,并试图关闭通道,当超时最终被触发时,我们的代码将会出现panic。

ch := make(chan bool, 1)
timeout := make(chan bool, 1)
defer close(ch)
defer close(timeout)

go func() {
  time.Sleep(1 * time.Second)
  timeout <- true
}()

go func() {
  ch <- true
}()

select {
case <-ch:
  fmt.Println("Read from ch")
case <-timeout:
  fmt.Println("Timed out")
}

错误信息

方法B: 就一行代码

有用的是,time package提供了After功能,它可以为我们创建超时通道:

ch := make(chan bool, 1)
defer close(ch)

go func() {
  ch <- true
}()

select {
case <-ch:
  fmt.Println("Read from ch")
case <-time.After(1 * time.Second):
  fmt.Println("Timed out")
}

由于在select语句之后我们没有保留通道,所以垃圾收集器将在超时之后为我们清理所有东西。对于不需要经常处理超时的长时间运行的应用程序,这应该没有问题。但是在很多情况下,我们想要确保我们把所有的东西都清理干净。

方法C:用时间定时器自己清理

如果你看一下godoc。之后,您可能已经被引导到此选项。引擎盖下是时间。使用定时器结构后,可以根据需要显式停止:

ch := make(chan bool, 1)
defer close(ch)

go func() {
  ch <- true
}()

timer := time.NewTimer(1 * time.Second)
defer timer.Stop()

select {
case <-ch:
  fmt.Println("Read from ch")
case <-timer.C:
  fmt.Println("Timed out")
}

这需要比前一个示例多一点的代码,但是您可以放心,当函数返回时,它所使用的所有通道都已被清除。

方法D:使用context

context提供了withTimeout的方法 ?

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*3))
    defer cancel()
    dataChan := make(chan bool)

    go func(ctx context.Context, dataChan chan bool) {
        select {
        case <-ctx.Done():
            fmt.Println("done")
        case <-dataChan:
            fmt.Println("data parse ok!")
        }

    }(ctx, dataChan)

    go func() {
        time.Sleep(time.Second * 4)
        dataChan <- true
    }()

    time.Sleep(time.Second * 5)

}

总结:

并发编程,路漫漫其修远兮!

GoJSON 标准库 library support 是支持配置文件的一中方式. 应用程序可以定义一个包含所有可配置元素的配置结构类型,并将JSON从一个文件加载到这个结构的实例中,例如:

type Config struct {
    ServerUrl   string
    APIKey        string
    MaxSessions int
}

func readConfig(filename string) (*Config, error) {
    // 实例化一个默认的配置结构体
    conf := &Config{Url: "http://localhost:8080/", MaxSessions: 10}

    b, err := ioutil.ReadFile("./conf.json")
    if err != nil {
        return nil, err
    }
    if err = json.Unmarshal(b, conf); err != nil {
        return nil, err
    }
    return conf, nil
}

这种方式相对于没有类型的数据结构(例如js或者python的数据结构)已经是一种提升,例如我们可以使用json.Unmarshal来实现简单的参数验证,如果配置文件是:

{ "MaxSessions": "20 seconds" }

json.Unmarshal将会return出一个错误提示: "20 seconds" is not a valid integer, 然后我们就可以将这种判断放在启动load文件而不是出现在运行环境中了.

定制

- 阅读剩余部分 -