Go provides concurrency using Goroutines, Channels, and Sync mechanisms.
This cheatsheet covers basic to advanced concepts with examples and expected outputs.
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from Goroutine!")
}
func main() {
go sayHello()
time.Sleep(1 * time.Second) // Prevent main from exiting before goroutine executes
fmt.Println("Main function finished")
}
Expected Output:
Hello from Goroutine!
Main function finished
🔹 Gotcha: If time.Sleep is removed, the main function exits before the goroutine executes.
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Mark as done when function exits
fmt.Printf("Worker %d is working...", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // Increment counter
go worker(i, &wg)
}
wg.Wait() // Wait for all goroutines to complete
fmt.Println("All workers finished!")
}
Expected Output:
Worker 1 is working...
Worker 2 is working...
Worker 3 is working...
All workers finished!
🔹 Use WaitGroup when waiting for multiple Goroutines to complete.
package main
import "fmt"
func main() {
messages := make(chan string)
go func() {
messages <- "Hello, Channel!"
}()
msg := <-messages // Receive from channel
fmt.Println(msg)
}
Expected Output:
Hello, Channel!
🔹 Channels synchronize Goroutines by default.
package main
import "fmt"
func main() {
ch := make(chan string, 2) // Buffered channel with capacity 2
ch <- "Task 1"
ch <- "Task 2"
fmt.Println(<-ch) // Output: Task 1
fmt.Println(<-ch) // Output: Task 2
}
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Task Done"
}()
fmt.Println(<-ch) // Blocks until message received
}
🔹 Unbuffered channels block until a sender & receiver are both ready.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "Message from Channel 1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Message from Channel 2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
Expected Output (Non-deterministic, depends on sleep times):
Message from Channel 1
🔹 Select allows handling multiple channels simultaneously.
sync.Mutexpackage main
import (
"fmt"
"sync"
)
var (
counter int
mutex sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock()
counter++
mutex.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final Counter:", counter)
}
Expected Output:
Final Counter: 5
🔹 Use Mutex to prevent concurrent writes to shared resources.
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
time.Sleep(time.Second) // Simulate work
fmt.Printf("Worker %d processed job %d", id, job)
results <- job * 2 // Return result
}
}
func main() {
const numWorkers = 3
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// Start workers
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Send jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
// Collect results
for res := range results {
fmt.Println("Result:", res)
}
}
Expected Output:
Worker 1 processed job 1
Worker 2 processed job 2
Worker 3 processed job 3
Worker 1 processed job 4
Worker 2 processed job 5
Result: 2
Result: 4
Result: 6
Result: 8
Result: 10
🔹 Worker Pools optimize concurrency without creating excessive Goroutines.
sync.WaitGroup to wait for Goroutines to complete.sync.Mutex to prevent race conditions on shared data.🚀 Happy Coding! 🎯