Timeout control in Go

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 leaks, resource non-release, etc.).

Timer

The method to implement timeout control in go is very simple. 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

Insert image description here
time.After() will return a Channel, which 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")
	}
}

It is assumed here that there is a goroutine running a time-consuming task, and the select feature has a channel that exits after obtaining data. When the goroutine does not complete the task within a limited time, the main goroutine will exit, thus achieving the purpose of timeout.

output:

do something...
timeout

timer.After cancels, and Channel sends a message at the same time. You can also close the channel and other notification methods.

Note that it is best for the Channel to have a certain size to prevent blocking the goroutine and causing leakage.

Context

The second option is to use context. Go's context function is powerful;
Insert image description here
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())
	}

With the same usage, context's Done() function will return a channel, which will take effect when the current work is completed or the context is cancelled.

timout context deadline exceeded

You can also know the reason why the current context is closed through timeout.Err().

goroutine passes context

Another advantage of using context is that you can take advantage of its natural feature of being passed among multiple goroutines, so that all goroutines that have passed the context 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 how many layers of goroutine are nested, you can get the message when the context is canceled (of course, the premise is that the context has to be passed away)

When you need to cancel the context in advance under some special circumstances, you can also manually call the cancelFunc() function.

Case in Gin

The 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")

Insert image description here

For example, the above code times out and waits for 10 seconds to release Gin resources. The implementation principle is the same as the above example.

Guess you like

Origin blog.csdn.net/m0_73728511/article/details/133365273