asyncio → Goroutines
Concurrent programming
Introduction
In this lesson, you'll learn about asyncio → goroutines in Go. Coming from Python, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Python, you're familiar with concurrent programming.
Go has its own approach to concurrent programming, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
package main
import (
"fmt"
"io"
"net/http"
"sync"
)
func fetch(url string) (string, error) {
resp, err := http.Get(url)
if err != nil { return "", err }
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
return string(body), err
}
func main() {
urls := []string{"http://api/a", "http://api/b"}
results := make([]string, len(urls))
var wg sync.WaitGroup
for i, url := range urls {
wg.Add(1)
go func(i int, url string) {
defer wg.Done()
results[i], _ = fetch(url)
}(i, url)
}
wg.Wait()
fmt.Println(results)
}Comparing to Python
Here's how you might have written similar code in Python:
import asyncio
import aiohttp
async def fetch(session, url: str) -> str:
async with session.get(url) as r:
return await r.text()
async def main():
async with aiohttp.ClientSession() as s:
# Parallel requests
results = await asyncio.gather(
fetch(s, "http://api/a"),
fetch(s, "http://api/b"),
)
print(results)
asyncio.run(main())You may be used to different syntax or behavior.
Go goroutines use real threads; Python asyncio is single-threaded
You may be used to different syntax or behavior.
asyncio.gather → sync.WaitGroup or channels
You may be used to different syntax or behavior.
Go http.Get is synchronous but cheap in goroutines; Python needs async client
You may be used to different syntax or behavior.
Go can use all CPU cores; Python GIL limits CPU parallelism
Step-by-Step Breakdown
1. go Keyword
The 'go' keyword launches a goroutine — a lightweight thread. Unlike Python's async, no await needed; the goroutine runs concurrently.
result = await fetch(session, url)go func() { result, _ = fetch(url) }()2. WaitGroup vs gather
sync.WaitGroup waits for a set of goroutines to complete — the Go equivalent of asyncio.gather.
results = await asyncio.gather(*tasks)var wg sync.WaitGroup
for ... { wg.Add(1); go func() { defer wg.Done(); work() }() }
wg.Wait()3. Channels for Results
Go channels safely pass data between goroutines — replacing asyncio's return values from gather.
ch := make(chan string, len(urls))
for _, url := range urls {
go func(u string) { r, _ := fetch(u); ch <- r }(url)
}
for range urls { fmt.Println(<-ch) }Common Mistakes
When coming from Python, developers often make these mistakes:
- Go goroutines use real threads; Python asyncio is single-threaded
- asyncio.gather → sync.WaitGroup or channels
- Go http.Get is synchronous but cheap in goroutines; Python needs async client
Key Takeaways
- go keyword launches goroutines (truly parallel)
- asyncio.gather → WaitGroup or channels
- Goroutines use OS threads; asyncio uses event loop
- Go uses all CPU cores; Python GIL prevents this