Goroutines in Go
Goroutines are a fundamental feature in the Go programming language, providing a lightweight mechanism for concurrent execution. This blog explores what goroutines are, how they work, and how to use them effectively.
What is a Goroutine?
A goroutine is a lightweight thread of execution in Go. Goroutines run concurrently with other goroutines and are managed by the Go runtime, which handles the scheduling and execution.
A goroutine is created using the go
keyword followed by a function call.
package main
import (
"fmt"
"time"
)
func printMessage(message string) {
for i := 0; i < 5; i++ {
fmt.Println(message, i)
time.Sleep(time.Millisecond * 500)
}
}
func main() {
go printMessage("Hello from Goroutine")
printMessage("Hello from Main")
}
Explanation:
- The
go printMessage(...)
call starts a new goroutine. - The
main
function continues executing independently of the goroutine.
Concurrency and Parallelism
- Concurrency: Multiple tasks making progress at the same time.
- Parallelism: Multiple tasks running simultaneously, usually on multiple CPU cores.
Goroutines provide concurrency, and Go’s runtime can manage multiple goroutines even on a single CPU core.
Synchronization with sync.WaitGroup
To wait for goroutines to finish, Go provides the sync.WaitGroup
.
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
fmt.Printf("Worker %d finished\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers finished")
}
Explanation:
sync.WaitGroup
is used to wait for multiple goroutines to finish.wg.Add(1)
indicates a new goroutine is starting.wg.Done()
marks a goroutine as finished.wg.Wait()
blocks until all goroutines have completed.
Channels for Communication
Channels provide a way for goroutines to communicate with each other and synchronize their execution.
package main
import "fmt"
func calculateSquare(num int, result chan int) {
result <- num * num
}
func main() {
results := make(chan int)
go calculateSquare(4, results)
square := <-results
fmt.Println("Square:", square)
}
Explanation:
- A channel is created with
make(chan int)
. - The channel
results
is used to send data from a goroutine. - The
calculateSquare
function sends data through the channel. - The main function receives the data using
<- results
.
Buffered Channels
Buffered channels allow sending multiple values without blocking immediately.
package main
import "fmt"
func main() {
messages := make(chan string, 2)
messages <- "Hello"
messages <- "World"
fmt.Println(<-messages)
fmt.Println(<-messages)
}
Explanation:
- A buffered channel with capacity 2 is created.
- Two values are sent without blocking.
- Values are retrieved using
<-
.
Closing Channels
Channels can be closed using close()
.
package main
import "fmt"
func main() {
messages := make(chan string)
go func() {
messages <- "Hello"
close(messages)
}()
for msg := range messages {
fmt.Println(msg)
}
}
Explanation:
close(messages)
closes the channel.- The
for
loop iterates until the channel is closed.
Conclusion
Goroutines are a powerful tool for concurrent programming in Go. By combining them with channels and the sync
package, you can write efficient and scalable concurrent applications. Mastering goroutines will help you fully leverage Go's concurrency model.