In today's article, I will guide you on how to stop a goroutine in Golang. A goroutine is a very lightweight thread that is managed by the Go runtime. Every program in Go has at least one routine called the main Goroutine. A goroutine can be a function or method that runs independently of the main goroutine. A goroutine can only stop by returning from its top-level function; it cannot be forced to suspend by another goroutine.
Example 1: Sending a stop signal to the channel
Usually, a channel is passed to the goroutine. When you want the goroutine to stop, you can send a value into the signal channel. The goroutine frequently monitors that channel, it stops when a signal is detected. Let's take a look at the example below to see how we can send a stop signal to a channel to stop a goroutine:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("This is from main function!")
quitChan := make(chan bool)
go func() {
for {
select {
case <-quitChan:
return
default:
// print a message every 3 seconds
fmt.Println("This is test goroutine")
time.Sleep(time.Second * 3)
}
}
}()
// sleep to print some message from the goroutine
time.Sleep(time.Second * 10)
// stop the goroutine
quitChan <- true
fmt.Println("Go rountine has stopped!")
// test if the gorountine stopped or not
time.Sleep(time.Second * 10)
fmt.Println("This is the end of main func, goroutine no longer run!")
}
Output:
This is from main function!
This is test goroutine
This is test goroutine
This is test goroutine
This is test goroutine
Go rountine has stopped!
This is the end of main func, goroutine no longer run!
Example 2: Close the goroutine by closing the channel
When you no longer send items to the channel, you can close it. Inside the goroutine, you can stop the goroutine by checking if the channel is stopped or not. Here is an example of how to stop the goroutine when closing the channel, we can use the waitgroup to make sure the main goroutine will wait until the goroutines stop:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
strChan := make(chan string)
go func() {
for {
element, ok := <-strChan
// check if channel is closed
if !ok {
println("Goroutines killed!")
wg.Done()
return
}
println(element)
}
}()
strChan <- "this"
strChan <- "is"
strChan <- "a"
strChan <- "message"
close(strChan)
// wait all goroutine to stop
wg.Wait()
// print the last message
fmt.Println("This is the end of main func!")
}
Output:
this
is
a
message
Goroutines killed!
This is the end of main func!
Example 3: Close the goroutine by canceling the context
In this example, we will create a channel by using the context. Once the context is closed, the channel will be terminated and the goroutine will be stopped as well. Let's take a look at the example below to see how we can cancel the channel by using the context:
package main
import (
"context"
"fmt"
"time"
)
func main() {
// a forever channel
channel := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done(): // if cancel() execute
channel <- struct{}{}
return
default:
fmt.Println("This is from the gorountine")
}
// print a message every second
time.Sleep(1 * time.Second)
}
}(ctx)
// a go routine to close the above context
go func() {
time.Sleep(5 * time.Second)
// close the context
cancel()
}()
// run the gorountine forever
<-channel
fmt.Println("This is the end of the main func")
}
Output:
Example 4: Using the tomb package to stop a goroutine
The tomb package offers a conventional API for clean goroutine termination. A Tomb tracks the lifecycle of a goroutine as alive, dying, or dead, and the reason for its death. We can install this package by running the following command:
go get gopkg.in/tomb.v1
Here's a simple example that uses the tomb
package to kill the goroutine:
package main
import (
"fmt"
"time"
"gopkg.in/tomb.v1"
)
func main() {
testTomb := tomb.Tomb{}
go func() {
defer testTomb.Done() // Must call only once
for {
select {
case <-testTomb.Dying():
return
default:
time.Sleep(1 * time.Second)
fmt.Println("This is from the goroutine!")
}
}
}()
time.Sleep(4 * time.Second)
testTomb.Kill(fmt.Errorf("Death from above"))
err := testTomb.Wait() // Will return the error that killed the tomb
time.Sleep(2* time.Second)
fmt.Println(err)
}
Output:
This is from the goroutine!
This is from the goroutine!
This is from the goroutine!
This is from the goroutine!
Death from above
We can see that after the tomb.Kill()
is invoked, the goroutine will no longer print any messages.
Summary
In this article, I have introduced several ways to terminate a goroutine. Because we usually use the goroutine with a channel so we can stop the goroutine by stopping the channel. To stop the channel, we can send a stop signal, using the built-in stop or cancel the context that the channel using. Another way to terminate the goroutine is by using the tomb
package.
References
https://pkg.go.dev/sync
https://pkg.go.dev/context
https://pkg.go.dev/gopkg.in/tomb.v1