Let’s talk about making your Go code faster with concurrency! 🚀 Imagine you’re checking 100 websites - doing it one by one is slow.
Let’s fix that.
The Problem: Slow Website Checking
First, let’s write a test for our website checker:
func TestCheckWebsites(t *testing.T) {
mockChecker := func(url string) bool {
return url != "bad://website"
}
urls := []string{
"http://good.com",
"http://also.good",
"bad://website",
}
want := map[string]bool{
"http://good.com": true,
"http://also.good": true,
"bad://website": false,
}
got := CheckWebsites(mockChecker, urls)
if !reflect.DeepEqual(want, got) {
t.Errorf("wanted %v, got %v", want, got)
}
}
We have a CheckWebsites
function that checks URLs:
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
for _, url := range urls {
results[url] = wc(url) // Checks each URL one at a time
}
return results
}
This works, but it’s slow - like waiting for each kettle to boil before making the next cup of tea. ☕️🐌
Making It Faster with Goroutines
The solution? Concurrency!
In Go, we use goroutines (lightweight threads) to do multiple things at once.
First attempt (that doesn’t work):
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
for _, url := range urls {
go func() {
results[url] = wc(url) // Oops! Race condition!
}()
}
time.Sleep(2 * time.Second) // Hacky fix
return results
}
This fails because:
- The main function finishes before goroutines complete
- Multiple goroutines try to write to the map simultaneously (big no-no in Go!)
The Fix: Channels to the Rescue!
Channels are Go’s way for goroutines to communicate safely:
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
resultChannel := make(chan result)
for _, url := range urls {
go func(u string) {
resultChannel <- result{u, wc(u)} // Send results through channel
}(url)
}
for i := 0; i < len(urls); i++ {
r := <-resultChannel // Receive results one by one
results[r.string] = r.bool
}
return results
}
This works because:
- Each website check runs in parallel
- Channels ensure safe communication between goroutines
- No more race conditions!
The Result: 100x Speed Boost!
Our benchmark shows:
- Original: ~2.25 seconds
- Concurrent version: ~0.023 seconds
That’s like making 100 cups of tea in the time it used to take to make 1! (If only real life worked this way…)
Key Takeaways
- Goroutines are cheap threads for concurrent tasks
- Channels safely pass data between goroutines
- Always test concurrent code (Go’s race detector helps!)
- Remember: Make it work → Make it right → Make it fast
Next up: select
- for even more concurrency control!