golang 的 deadline/timeout(超时)

golang 的 deadline/timeout(超时) 弹性模式。

创建 deadline 只需一个参数:等待的时间。

💡 前几天发布后,发现末尾的地方,写得不是很清楚,于是删掉了,今天补充后,重新进行发布

仓库给的使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go复制代码dl := deadline.New(1 * time.Second)

err := dl.Run(func(stopper <-chan struct{}) error {
// do something potentially slow
// give up when the `stopper` channel is closed (indicating a time-out)
// 做一些可能很慢的事情
// 当 `stopper` 通道关闭(表示超时)时放弃
return nil
})

switch err {
case deadline.ErrTimedOut:
// execution took too long, oops
// 执行时间太长了
default:
// some other error
// 其他错误
}

首先通过 New 函数,新建一个超时器,传入的参数,是所期待的超时时长

1
2
3
4
go复制代码func New(timeout time.Duration) *Deadline {
return &Deadline{
timeout: timeout,
}

然后调用超时器的 Run 方法即可,在运行时间超出所期待的超时时长就会退出,返回 ErrTimedOut 的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
go复制代码// ErrTimedOut is the error returned from Run when the deadline expires.
var ErrTimedOut = errors.New("timed out waiting for function to finish")

// Run runs the given function, passing it a stopper channel. If the deadline passes before
// the function finishes executing, Run returns ErrTimeOut to the caller and closes the stopper
// channel so that the work function can attempt to exit gracefully. It does not (and cannot)
// simply kill the running function, so if it doesn't respect the stopper channel then it may
// keep running after the deadline passes. If the function finishes before the deadline, then
// the return value of the function is returned from Run.
// Run 运行给定函数,并传递给它一个 stopper 通道。如果在
// 函数执行完毕之前截止时间已过,Run 会向调用者返回 ErrTimeOut,并关闭 stopper
// 通道,以便工作函数可以优雅地退出。它不会(也不能)
// 简单地杀死正在运行的函数,因此如果它不尊重 stopper 通道,那么它可能
// 在截止日期过后继续运行。如果函数在截止日期前结束,那么
// 函数的返回值将从 Run 返回。
func (d *Deadline) Run(work func(<-chan struct{}) error) error {
result := make(chan error)
stopper := make(chan struct{})

go func() {
value := work(stopper)
select {
case result <- value:
case <-stopper:
}
}()

select {
case ret := <-result:
return ret
case <-time.After(d.timeout):
close(stopper)
return ErrTimedOut
}
}

测试用例所要执行的 work 函数定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码func takesFiveMillis(stopper <-chan struct{}) error {
time.Sleep(5 * time.Millisecond)
return nil
}

func takesTwentyMillis(stopper <-chan struct{}) error {
time.Sleep(20 * time.Millisecond)
return nil
}

func returnsError(stopper <-chan struct{}) error {
return errors.New("foo")
}

可以看出我们要执行的函数的参数都必须是 chan 类型,然后定义的字段名是 stopper,其实定义为其他字段名也是一样的

如果只是想简单地将我们执行的函数超时就不执行了,那么直接定义完相应的执行函数和新建超时器进行执行即可,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
go复制代码func takesFiveMillis(stopper <-chan struct{}) error {
time.Sleep(5 * time.Millisecond)
return nil
}

func takesTwentyMillis(stopper <-chan struct{}) error {
time.Sleep(20 * time.Millisecond)
return nil
}

func returnsError(stopper <-chan struct{}) error {
return errors.New("foo")
}

func TestDeadline(t *testing.T) {
dl := New(10 * time.Millisecond)

if err := dl.Run(takesFiveMillis); err != nil {
t.Error(err)
}

if err := dl.Run(takesTwentyMillis); err != ErrTimedOut {
t.Error(err)
}
}

但是如果你想在超时后执行一些操作,那么就可以利用我们执行函数的传入 stopper 参数,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码dl := New(10 * time.Millisecond)
done := make(chan struct{})
err := dl.Run(func(stopper <-chan struct{}) error {
<-stopper
// 可以在这里执行在超时后,进行的一些处理
close(done)
return nil
})·
if err != ErrTimedOut {
t.Error(err)
}
<-done
}

这里有一个大家可能会很疑惑的点,就是超时之后,Run 函数不应该是立即退出了吗?其实是的,超时之后就立即退出了,但是我们的 work 函数执行的时候是用协程去执行的,所以我们的 work 还是在执行中,

所以就需要 done 通道,去判断 work 函数是否执行完了,否则这个测试用例就直接退出了, 为了我这里说的是否正常,我们将测试用例注释相应的改动:

Untitled.png

查看执行结果后,可以看出我这里所说的是正确的

这里大家可能还有一个疑惑点,就是我们这里的 stopper 阻塞在这里,那么超时之后,不是就进行关闭了吗?

Untitled 1.png

Untitled 2.png

这里就设计到 channel 的知识点了,读已经关闭的 channel 进行读操作的时候,依然是可以读的,我们这里的 stopper 是无缓冲区的 channel,那么读出来的就是相应类型的零值,但是如果是有缓存区的呢?如果关闭前,有缓存区的 channel 里面有数据,我们仍然能读出来,读到没有数据的时候,再读就是相应类型的零值。

这里我们引申一下,如果去写已经关闭的缓存区呢?这种情况就会 panic:send on closed channel 的情况,对于有无缓冲区的 channel 都是一样的结果

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%