Preface
In daily development, we will most likely encounter timeout control scenarios, such as a batch of time-consuming tasks, network requests, etc.; a good timeout control can effectively avoid some problems (such as goroutine
leakage, resource unreleased, etc.).
Timer
The method of implementing timeout control in go is very simple. First of all, the first solution is Time.After(d Duration)
:
func main() {
fmt.Println(time.Now())
x := <-time.After(3 * time.Second)
fmt.Println(x)
}
output:
2021-10-27 23:06:04.304596 +0800 CST m=+0.000085653
2021-10-27 23:06:07.306311 +0800 CST m=+3.001711390
time.After()
will return a Channel
, and the Channel
will write data after a delay of d.
With this feature, some asynchronous control timeout scenarios can be realized:
func main() {
ch := make(chan struct{}, 1)
go func() {
fmt.Println("do something...")
time.Sleep(4*time.Second)
ch<- struct{}{}
}()
select {
case <-ch:
fmt.Println("done")
case <-time.After(3*time.Second):
fmt.Println("timeout")
}
}
Suppose here that there is a goroutine
running a time-consuming task. Using select, there is a channel
gets the data and exits. When goroutine
does not complete the task in a limited time, the main goroutine
will exit, which achieves the purpose of timeout.
output:
do something...
timeout
Timer.After is canceled, and the Channel sends a message at the same time. You can also close the channel and other notification methods.
Note that Channel should have a size to prevent blocking the goroutine and causing leakage.
Context
The second solution is to use context, go's context function is powerful;
Using the context.WithTimeout()
method will return a context with a timeout function.
ch := make(chan string)
timeout, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go func() {
time.Sleep(time.Second * 4)
ch <- "done"
}()
select {
case res := <-ch:
fmt.Println(res)
case <-timeout.Done():
fmt.Println("timout", timeout.Err())
}
The same usage, context
of Done()
function returns a channel
, the channel
will be completed in the context of current work or cancellation takes effect.
timout context deadline exceeded
Through timeout.Err()
can also know the reason why context
goroutine passes context
Another advantage of using context
is that it can take advantage of its natural transmission characteristics in multiple goroutines, so that all goroutines that pass the context can receive cancellation notifications at the same time. This is very widely used in multi-go.
func main() {
total := 12
var num int32
log.Println("begin")
ctx, cancelFunc := context.WithTimeout(context.Background(), 3*time.Second)
for i := 0; i < total; i++ {
go func() {
//time.Sleep(3 * time.Second)
atomic.AddInt32(&num, 1)
if atomic.LoadInt32(&num) == 10 {
cancelFunc()
}
}()
}
for i := 0; i < 5; i++ {
go func() {
select {
case <-ctx.Done():
log.Println("ctx1 done", ctx.Err())
}
for i := 0; i < 2; i++ {
go func() {
select {
case <-ctx.Done():
log.Println("ctx2 done", ctx.Err())
}
}()
}
}()
}
time.Sleep(time.Second*5)
log.Println("end", ctx.Err())
fmt.Printf("执行完毕 %v", num)
}
In the above example, no matter goroutine
nested, you can context
cancelled (of course, the premise is that context
to be passed away)
In some special cases, when you need to cancel the context in advance, you can also manually call the cancelFunc()
function.
Case in Gin
Shutdown(ctx)
function provided by Gin also makes full use of context
.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
For example, the above code is to wait for 10s to Gin
the resources of 06179ef1cce372, and the principle of implementation is the same as the above example.
Summarize
Because the time to write go is not long, so I wrote a hand-training project: an interface stress test tool.
One of the very common requirements is to exit after N seconds from the stress test. This is just the application of related knowledge points. It is also a reference go
https://github.com/crossoverJie/ptg/blob/d0781fcb5551281cf6d90a86b70130149e1525a6/duration.go#L41
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。