Goroutine: Concurrency vs Parallelism
Concurrency: Goroutines allow multiple tasks to progress independently. They don't necessarily run at the same time, but they give the appearance of doing so by sharing the same thread and switching between tasks.
Parallelism: Goroutines can perform multiple tasks at the same time on multi-core CPUs if enough resources are allocated.
Key Factors for Parallelism
- Number of CPU Cores:
- Go's runtime can execute goroutines in parallel on multiple CPU cores.
- By default, the runtime uses a number of threads equal to the number of available CPU cores.
GOMAXPROCS
Setting:- The
runtime.GOMAXPROCS
function determines how many operating system threads the Go runtime can use for parallel execution. - By default,
GOMAXPROCS
is set to the number of logical CPUs.
- The
- Task Type:
- CPU-bound tasks (e.g., mathematical calculations) can benefit from parallelism.
- I/O-bound tasks (e.g., file reads, network requests) rely more on concurrency and don't require multiple cores to be efficient.
Example: Parallel Goroutines
Example 1: Parallelism with CPU-bound tasks
- This program demonstrates parallel execution by utilizing all available cores.
- Four goroutines perform a simulated task in parallel.
- If the machine has 4 or more cores, all goroutines will execute simultaneously.
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func compute(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Goroutine %d starting\n", id)
time.Sleep(2 * time.Second) // Simulate a CPU-bound task
fmt.Printf("Goroutine %d finished\n", id)
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all available CPU cores
fmt.Println("Running with", runtime.NumCPU(), "CPUs")
var wg sync.WaitGroup
for i := 1; i <= 4; i++ {
wg.Add(1)
go compute(i, &wg)
}
wg.Wait()
fmt.Println("All goroutines finished.")
}
Example 2: Concurrency without Parallelism
- If GOMAXPROCS is set to 1, all goroutines will execute sequentially, even if there are multiple cores.
- In this case, the runtime uses only one thread, and tasks appear concurrent but are executed one at a time.
runtime.GOMAXPROCS(1) // Force the runtime to use only one thread
Key Differences Between
Aspect | Concurrency | Parallelism |
---|---|---|
Execution | Tasks progress independently. | Tasks execute at the same time. |
CPU Utilization | Works well even on single-core CPUs. | Requires multiple CPU cores. |
Focus | Managing multiple tasks efficiently. | Speeding up task execution. |
Example | I/O-bound tasks like handling requests. | CPU-bound tasks like matrix calculations. |
Parallelism with Goroutines: Practical Notes
- Not Always Needed: Many real-world applications are more I/O-bound (e.g., web servers). In such cases, concurrency is more critical than parallelism.
- Limits of Parallelism: Beyond a certain point, adding more goroutines won't improve performance due to factors like contention, overhead, and resource limits.
- Profiling: Use tools like
pprof
ortrace
to measure and optimize performance in Go programs.