LanguagesHow to Timeout a Goroutine in Go

How to Timeout a Goroutine in Go

Go Programming ``

On occasion, a goroutine has to wait for a long time before being executed. In this Golang programming tutorial, we will introduce some of the concepts behind goroutine execution, the model it works upon in Go, and the need of timeout in the execution process. Also, we will lay out some implementation techniques that will save us from waiting forever for a goroutine, which also provides a control over the time to wait for a goroutine to finish its job.

What are Goroutines?

A thread is the basic element to implement concurrency at a high level in a programming model. In Go, this basic element thread is given another name, known as a goroutine. Although their core agenda is the same – to act as a basic element in the arena of concurrent programming – their implementation is different. That is why it is given a separate name (goroutine) rather than the common name called threads. To put it in fewer words – goroutines are user-space threads, typically like the kernel threads managed by the operating system. Goroutines, meanwhile, are entirely managed by the Go runtime. This makes goroutines not only lightweight, but also much cheaper than kernel threads. Further, a goroutine has a smaller memory footprint, with an initial stack size of 2kb, while, on the other hand, a thread (by default) begins with a stack size of 8kb.

You can learn more about goroutines by reading our tutorial: Introduction to Goroutines in Go.

What is the Fork-join Model in Go?

Goroutines are the basic element, but the model on which these goroutines play to implement the environment of concurrent execution is called the fork-join model of concurrency. To visualize this better, imagine goroutines are the players and the rules of the game are laid out by the fork-join model. In the fork-join model, a child goroutine is forked (creates a separate execution path) from its parent to do the job concurrently, and rejoins back once it is done or at some point. Meanwhile, the parent goroutine must wait until all its child goroutines return.. This is an ideal situation, but reality has its own ugly face, and this does not always play out as planned. Welcome, to timeout.

Timeout in Go

Raising interrupt in the middle of the execution of a goroutine is always problematic. In the fork-join model of concurrency, as we fork (or create) a goroutine, we lose control over the scheduling of the goroutine until the routine gets back to the join point. These join points are actually the mechanism to implement synchronization between goroutines over a channel.

In addition, timeouts are also important for programs that:

  • Seek to bound the execution time within a limit
  • Connects to external resources
  • Take too much time, especially in a server, which needs to be timed out immediately to maintain serviceability of the server

Why is timeout Necessary?

Goroutines execute in parallel and these goroutines are used to create routines that provide some form of services in the server backend. This request of services can execute parallely in the form of one or more goroutines forked from their immediate parent. The sub goroutines may have their own internal requests and may have been occupying resources. A timeout request must revert back to a position releasing all the resources such as goroutines, files, database connections, and so on, that it has occupied immediately.

If there is no timeout, every process in the line of execution tree will keep on waiting. By the way, waiting is relaxing (only for a few moments). Imagine you are waiting for a video to load in your browser; until it does, you get to enjoy the circle animation. This kind of situation is not ideal but occurs if there is no utility to timeout an execution which is taking too much time for what could be any number of reasons.

Read: Best Tools for Remote Developers

Examples of How to timeout Out a Goroutine in Go: Using time.Sleep()

In this example, we use the time.Sleep() method to emulate the time that the function normally takes to complete the job:

package main

import (
	"fmt"
	"time"
)

func main() {
	channel1 := make(chan string)

	go func() {
		time.Sleep(time.Second * 2)
		channel1 <- "CHANNEL 1"
	}()

	select {
	case str := <-channel1:
		fmt.Println(str)
	case <-time.After(time.Second * 1):
		fmt.Println("TIMEOUT: CHANNEL 1")
	}

	channel2 := make(chan string)
	go func() {
		time.Sleep(time.Second * 2)
		channel2 <- "CHANNEL 2"
	}()

	select {
	case str := <-channel2:
		fmt.Println(str)
	case <-time.After(time.Second * 3):
		fmt.Println("TIMEOUT: CHANNEL 2")
	}
}

Observe here that the time is hardcoded in to the anonymous functions (example: time.Second * 2). This denotes the time execution will take before printing the message into the particular channel, such as channel1 or channel2. Also, we have used the time.After() function to make the execution wait for it to finish. As this function executes, it means that the execution has completed and the wait time is over.

Note that both the channels – channel1 and channel2 take about two seconds to execute with a timeout call after one second, and the later timeout after three seconds, respectively. This means that the time.After(time.Second*3) call returns after receiving value from the channel2, found in the first case of the select block. Because of this, there will not be any timeout for channel2. But, as is the case of the value of the time.After() call with the first channel1, there is enough time to return from the time.Sleep() function, and hence the timeout message is printed.

Timing Out a Goroutine in Go with User Input

In this example, we will provide the duration after which the timeout will occur through user input. This provides users control over the execution of goroutines in Golang applications:

package main

import (
	"fmt"
	"sync"
	"time"
)

func timeout(w *sync.WaitGroup, t time.Duration) bool {
	c := make(chan int)
	go func() {
		defer close(c)
		time.Sleep(5 * time.Second)
		w.Wait()
	}()
	select {
	case <-c:
		return false
	case <-time.After(t):
		return true
	}
}

func main() {
	var t int32
	fmt.Printf("Enter duration for timeout(in milisecond):")
	fmt.Scanf("%d", &t)

	var w sync.WaitGroup
	w.Add(1)
	d := time.Duration(t) * time.Millisecond
	fmt.Printf("\n...will timeout after %s ...\n", d)
	if timeout(&w, d) {
		fmt.Println("TIMEOUT!")
	} else {
		fmt.Println("DONE!")
	}
	w.Done()
	if timeout(&w, d) {
		fmt.Println("TIMEOUT!")
	} else {
		fmt.Println("DONE!")
	}
}

Observe that, unlike the previous example, here the time duration used by the time.After() function is supplied as an argument to the user defined timeout function. This makes the duration of timeout flexible to user input. Moreover, the w.wait() call makes the timeout function wait for matching with the sync.Done() call.

Final Thoughts on timeout and Goroutines in Golang

To get the essence of parallel execution, the runtime environment does many things behind the scenes and is not simple as it looks from the point of view of coding through goroutines. Timing out a goroutine before it finishes is not only necessary, but also vital in some critical situations. Here, in this Go programming tutorial, we have provided two examples showing how developers can have some control over goroutine execution by timing them out as required.

Read more Go and Golang programming tutorials and software development guides.

Latest Posts

Related Stories