Goroutines
Goroutines are lightweight threads managed by the Go runtime. They're one of Go's most powerful features, making concurrent programming simple and efficient. In this lesson, you'll learn how goroutines work and how to use them effectively.
What is a Goroutine?
A goroutine is a lightweight thread of execution. Unlike OS threads, goroutines are managed by the Go runtime and are extremely cheap to create.
- Lightweight - Start with ~2KB stack (grows as needed)
- Cheap - Can create millions of goroutines
- Multiplexed - Many goroutines run on few OS threads
- Managed - Go runtime handles scheduling
Creating Goroutines
Use the go keyword to start a goroutine:
Click Run to execute your code
Common Goroutine Patterns
1. Anonymous Function Goroutines
Click Run to execute your code
2. Multiple Goroutines
func main() {
for i := 0; i < 5; i++ {
go func(id int) {
fmt.Printf("Goroutine %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Goroutine %d done\n", id)
}(i) // Pass i as argument!
}
time.Sleep(2 * time.Second) // Wait for goroutines
}
Synchronizing with WaitGroups
Use sync.WaitGroup to wait for goroutines to finish:
Click Run to execute your code
Add(n)- Increment counter by nDone()- Decrement counter by 1Wait()- Block until counter is 0
How Goroutines Are Scheduled
Common Mistakes
1. Not waiting for goroutines
// β Wrong - main exits before goroutine runs
func main() {
go fmt.Println("Hello")
// Program exits immediately!
}
// β
Correct - wait for goroutine
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Hello")
}()
wg.Wait()
}
2. Capturing loop variables
// β Wrong - all goroutines see final value
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // All print 5!
}()
}
// β
Correct - pass as argument
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Println(n) // Prints 0, 1, 2, 3, 4
}(i)
}
3. Goroutine leaks
// β Wrong - goroutine never exits
func leak() {
go func() {
for {
// Infinite loop with no exit condition
time.Sleep(time.Second)
}
}()
}
// β
Correct - provide exit mechanism
func noLeak(ctx context.Context) {
go func() {
for {
select {
case <-ctx.Done():
return // Exit when context cancelled
default:
time.Sleep(time.Second)
}
}
}()
}
Exercise: Concurrent URL Fetcher
Task: Create a concurrent URL fetcher using goroutines.
Requirements:
- Fetch multiple URLs concurrently
- Use WaitGroup to wait for all fetches
- Print the status of each fetch
- Measure total time taken
Show Solution
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
func fetchURL(url string, wg *sync.WaitGroup) {
defer wg.Done()
start := time.Now()
resp, err := http.Get(url)
duration := time.Since(start)
if err != nil {
fmt.Printf("β %s - Error: %v (%.2fs)\n", url, err, duration.Seconds())
return
}
defer resp.Body.Close()
fmt.Printf("β
%s - Status: %s (%.2fs)\n", url, resp.Status, duration.Seconds())
}
func main() {
urls := []string{
"https://golang.org",
"https://google.com",
"https://github.com",
"https://stackoverflow.com",
"https://reddit.com",
}
var wg sync.WaitGroup
start := time.Now()
fmt.Println("Fetching URLs concurrently...")
fmt.Println("==============================")
for _, url := range urls {
wg.Add(1)
go fetchURL(url, &wg)
}
wg.Wait()
totalDuration := time.Since(start)
fmt.Println("==============================")
fmt.Printf("Total time: %.2fs\n", totalDuration.Seconds())
fmt.Printf("Fetched %d URLs concurrently\n", len(urls))
}
Summary
- Goroutines are lightweight threads managed by Go runtime
- go keyword starts a new goroutine
- Very cheap - can create millions of goroutines
- WaitGroup synchronizes goroutine completion
- Pass loop variables as arguments to goroutines
- M:N scheduling - many goroutines on few OS threads
- Always provide exit mechanisms to avoid leaks
What's Next?
Now that you understand goroutines, you're ready to learn about ChannelsβGo's way of communicating between goroutines. Channels make concurrent programming safe and elegant!
Enjoying these tutorials?