preface

In daily development, we are likely to encounter timeout control scenarios, such as a batch time-consuming task, network requests, etc. A good timeout control can effectively avoid problems such as goroutine leaks, resource non-release, etc.

Timer

The way to implement timeout control in GO is very simple. First, the first scheme is time. After(d Duration) :

func main(a) {
	fmt.Println(time.Now())
	x := <-time.After(3 * time.Second)
	fmt.Println(x)
}
Copy the code

output:

2021-10-27 23:06:04.304596 +0800 CST M =+0.000085653 2021-10-27 23:06:07.306311 +0800 CST M =+3.001711390Copy the code

Time.after () returns a Channel that writes data After a delay of d.

With this feature you can implement some asynchronous control timeout scenarios:

func main(a) {
	ch := make(chan struct{}, 1)
	go func(a) {
		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")}}Copy the code

Here we assume that a Goroutine is running a time-consuming task. Using the select channel, the main Goroutine will exit if the goroutine does not complete the task within a limited time, and the purpose of timeout is achieved.

output:

do something...
timeout
Copy the code

Timer. After is cancelled, and a Channel sends a message at the same time. It can also close the Channel and other notification methods.

Note that channels are best sized to prevent blocking the Goroutine and causing leaks.

Context

The second option is to use context, go’s context is powerful;usingcontext.WithTimeout()Method returns a context with a timeout function.

	ch := make(chan string)
	timeout, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	go func(a) {
		time.Sleep(time.Second * 4)

		ch <- "done"} ()select {
	case res := <-ch:
		fmt.Println(res)
	case <-timeout.Done():
		fmt.Println("timout", timeout.Err())
	}
Copy the code

In the same way, the context’s Done() function returns a channel that takes effect when the current work is Done or when the context is cancelled.

timout context deadline exceeded
Copy the code

Timeout.err () also tells you why the current context is closed.

Goroutine to pass the context

Another advantage of using a context is that it allows all goroutines that pass the context to receive cancellation notifications at the same time, taking advantage of its natural ability to pass through multiple Goroutines. This is very common in multi-Go.

func main(a) {
	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(a) {
			//time.Sleep(3 * time.Second)
			atomic.AddInt32(&num, 1)
			if atomic.LoadInt32(&num) == 10 {
				cancelFunc()
			}
		}()
	}
	for i := 0; i < 5; i++ {
		go func(a) {

			select {
			case <-ctx.Done():
				log.Println("ctx1 done", ctx.Err())
			}

			for i := 0; i < 2; i++ {
				go func(a) {
					select {
					case <-ctx.Done():
						log.Println("ctx2 done", ctx.Err())
					}
				}()
			}

		}()
	}

	time.Sleep(time.Second*5)
	log.Println("end", ctx.Err())
	fmt.Printf("Execute completed %v", num)
}
Copy the code

In the above example, no matter how many layers the Goroutine has nested, the message can be retrieved when the context is cancelled (if the context is passed away, of course).

You can also call the cancelFunc() function manually when special circumstances require canceling the context in advance.

Cases in Gin

The Shutdown(CTX) function provided by Gin also makes full use of the context.

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	iferr := srv.Shutdown(ctx); err ! =nil {
		log.Fatal("Server Shutdown:", err)
	}
	log.Println("Server exiting")
Copy the code

For example, the above code will wait 10 seconds to release Gin resources. The implementation principle is the same as the above example.

conclusion

Since I have not been writing GO for a long time, I have written a project to practice my skills: an interface pressure test tool.

One of the most common requirements is the pressure test N seconds after exit, here just apply the relevant knowledge points, is also a beginner to go partners can refer to.

Github.com/crossoverJi…