programming

Getting to Know Package Context With Cancel On Golang

Package introduction context.WithCancel

Context which can add values, we can also add cancel signals to the context. Usually context cancel is used when we need to run another process, and we want to cancel the process. This context cancel is usually run using a different goroutine so that we easily want to cancel the execution of the goroutine, so we just send a cancel signal to the context then the goroutine we want to stop can be done.

Why do we have to use context?

We use Context.WithCancel to limit the process when we want to stop the process in a goroutine and we want to continue the process to the next process or another process. If we want to create a context with a cancel signal, then we need to use the context.WithCancel(parent) function.

Implementation and samples

Before going to the implementation of context.WithCancel, we will first simulate how when our program code occurs memory leak. Here is the code below. We will implement it with the sample code below.

func main() {
	fmt.Println("Total Goroutines", runtime.NumGoroutine())
	destination := CreateCounter()
	for n := range destination {
		fmt.Println("Counter", n)
		if n == 10 {
			break
		}
	}
	fmt.Println("Total Goroutines", runtime.NumGoroutine())
}

func CreateCounter() chan int {
	destination := make(chan int)
	go func() {
		defer close(destination)
		counter := 1
		for {
			destination <- counter
			counter++
		}
	}()
	return destination
}

In the above program, we will create a counter by using one goroutine when called in the main function. In the main program we will run only 10 counters so that there are not too many when printing to the terminal. So as a result when we run the program it will print as below.

✗ go run app.go   
Total Goroutine 1
Counter 1
Counter 2
Counter 3
Counter 4
Counter 5
Counter 6
Counter 7
Counter 8
Counter 9
Counter 10
Total Goroutine 2

It can be seen that when printing the total goroutine used is 1 only because the main app that we run will actually run in goroutine. Furthermore, when the counter finishes running, the total goroutine will increase to 2 because we run the counter program using 1 goroutine, so it automatically increases. But when we continue to call goroutines in this program it will continue to grow resulting in our memory consumption will also increase which is called memory leak.

So we need a mechanism to delete or stop the unused * goroutine* process. How can we do this? One of them is by using context.WithCancel.

Then, how to implement context.WithCancel in the previous program code? Let’s look at the program code that has been improved by adding context.WithCancel below.

func main() {
	parent := context.Background() // context parent
	ctx, cancel := context.WithCancel(parent) // context child with cancel

	fmt.Println("Total Goroutines", runtime.NumGoroutines())

	destination := CreateCounter(ctx)
	for n := range destination {
		fmt.Println("Counter", n)
		if n == 10 {
			break
		}
	}
	cancel()

	time.Sleep(3 * time.Second)
	fmt.Println("Total Goroutines", runtime.NumGoroutine())
}

func CreateCounter(ctx context.Context) chan int {
	destination := make(chan int)
	go func() {
		defer close(destination)
		counter := 1
		for {
			select {
			case < ctx.Done():
				return
			default:
				destination <- counter
				counter++
			}
		}
	}()
	return destination
}

The addition to the code above is that we will send context to the function using the parameter. Then we use the context in the goroutine and we add the select selection which is used to ensure that when the process in the main function calls for cancel() then the goroutine process will stop and delete the process in memory.

So we try to run the program after the fix with the results below.

✗ go run app.go
Total Goroutine 1
Counter 1
Counter 2
Counter 3
Counter 4
Counter 5
Counter 6
Counter 7
Counter 8
Counter 9
Counter 10
Total Goroutine 1

It can be seen that at the end of the total goroutine, only one will be seen because the previous process that ran the goroutine because we called cancel() then the process will stop automatically the goroutine will stop and delete from memory.

Conclusion

context.WithCancel is very necessary to use if we run processes in parallel or not even this becomes an obligation for golang developers so that each passing parameter in the function is added context in order to cancel the process, and avoid goroutines that continue to run without being used because the process is not stopped.

comments powered by Disqus