Go: GoRoutines, Channels, and Mutexes with Practical Examples


Concurrency is one of Go's superpowers. Whether you’re building high-performance APIs or simply speeding up tasks, GoRoutines, Channels, and Mutexes are essential tools in your toolbox. This blog will guide you through these concepts with examples, leading to a project where we fetch APIs concurrently.
1. What Are GoRoutines?
Think of GoRoutines as lightweight threads that let you run multiple tasks at the same time. Unlike threads, they’re super efficient, allowing you to spin up thousands of them without consuming a lot of memory.
Example: Baking Cookies with GoRoutines
Imagine you're running a bakery, where different tasks like baking cookies, cupcakes, and bread can run concurrently:
package main
import (
"fmt"
"time"
)
func bakeCookies() {
fmt.Println("Baking cookies...")
time.Sleep(2 * time.Second) // Simulates work
fmt.Println("Cookies are ready!")
}
func main() {
go bakeCookies() // Start a helper to bake cookies
fmt.Println("I'm busy with other tasks!")
time.Sleep(3 * time.Second) // Wait to see all work complete
}
Output:
I'm busy with other tasks!
Baking cookies...
Cookies are ready!
With GoRoutines, tasks can run independently without blocking each other.
2. Channels: Talking Between GoRoutines
GoRoutines are powerful, but how do they communicate? Channels act like walkie-talkies, enabling them to send and receive messages safely.
Example: Sending a Message
package main
import (
"fmt"
)
func sendOrder(ch chan string) {
ch <- "Cookies are ready!" // Send a message through the channel
}
func main() {
ch := make(chan string) // Create a channel
go sendOrder(ch) // Start GoRoutine to send a message
msg := <-ch // Receive the message
fmt.Println(msg)
}
Output:
Cookies are ready!
Channels ensure seamless communication between GoRoutines while maintaining thread safety.
3. Mutex: Managing Shared Resources
Concurrency can create conflicts when GoRoutines share resources. For example, two helpers trying to use the same oven simultaneously. Mutexes (mutual exclusion) solve this by allowing only one GoRoutine to access a resource at a time.
Example: Protecting a Counter
package main
import (
"fmt"
"sync"
)
var counter = 0 // Shared resource
var mutex = &sync.Mutex{}
func increment(wg *sync.WaitGroup) {
mutex.Lock() // Lock the resource
counter++ // Critical section
mutex.Unlock() // Unlock the resource
wg.Done() // Notify the WaitGroup that we're done
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // Add a worker to the WaitGroup
go increment(&wg)
}
wg.Wait() // Wait for all GoRoutines to finish
fmt.Println("Final Counter:", counter)
}
Output:
Final Counter: 5
The mutex.Lock()
and mutex.Unlock()
calls ensure only one GoRoutine updates the counter at a time.
4. Final Project: Concurrent API Calls
Now, let’s bring it all together! Here, we’ll fetch multiple API endpoints concurrently using GoRoutines, collect responses using Channels, and track successful requests using a Mutex.
Code: Concurrent API Calls
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
var successCount int // Shared counter for successful API calls
var mutex = &sync.Mutex{} // Mutex to protect the shared counter
var wg sync.WaitGroup // WaitGroup to wait for all GoRoutines to finish
func fetchAPI(url string, ch chan string) {
defer wg.Done() // Mark this GoRoutine as done
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(url)
if err != nil {
ch <- fmt.Sprintf("Failed to fetch %s: %v", url, err)
return
}
defer resp.Body.Close()
// If the response is successful, update the counter
if resp.StatusCode == http.StatusOK {
mutex.Lock()
successCount++
mutex.Unlock()
ch <- fmt.Sprintf("Success: %s", url)
} else {
ch <- fmt.Sprintf("Failed: %s - Status Code: %d", url, resp.StatusCode)
}
}
func main() {
// List of API endpoints to fetch
apiEndpoints := []string{
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
"https://jsonplaceholder.typicode.com/posts/4",
"https://invalid-url.com", // Invalid URL to simulate an error
}
// Channel to collect responses
responseChannel := make(chan string)
// Start a GoRoutine for each API call
for _, url := range apiEndpoints {
wg.Add(1)
go fetchAPI(url, responseChannel)
}
// GoRoutine to close the channel after all work is done
go func() {
wg.Wait() // Wait for all GoRoutines to finish
close(responseChannel) // Close the channel
}()
// Read responses from the channel
fmt.Println("API Call Results:")
for res := range responseChannel {
fmt.Println(res)
}
// Print the final count of successful API calls
fmt.Printf("\nNumber of successful API calls: %d\n", successCount)
}
What’s Happening Here?
GoRoutines: Each API call runs concurrently in a separate GoRoutine.
Channels: Collect responses (success or error) from each API call.
Mutex: Ensures the
successCount
is updated safely when an API call succeeds.WaitGroup: Waits for all API calls to complete before closing the channel.
Output Example:
API Call Results:
Success: https://jsonplaceholder.typicode.com/posts/1
Success: https://jsonplaceholder.typicode.com/posts/2
Success: https://jsonplaceholder.typicode.com/posts/3
Success: https://jsonplaceholder.typicode.com/posts/4
Failed to fetch https://invalid-url.com: Get "https://invalid-url.com": dial tcp: lookup invalid-url.com: no such host
Number of successful API calls: 4
Conclusion
By combining GoRoutines, Channels, and Mutexes, we’ve built a robust concurrent system for fetching APIs. This structure can be extended to more complex systems, such as:
Real-time data processing
Parallel file uploads/downloads
Microservices orchestration
Mastering these concepts will empower you to build fast, efficient, and scalable applications in Go. Happy coding:) 🚀
Subscribe to my newsletter
Read articles from kalyan dahake directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

kalyan dahake
kalyan dahake
I'm building systems across industries, ensuring seamless software delivery. I manage everything from system design to deployment, driving operational excellence and client satisfaction.