What Is Concurrency in Go?
Concurrency is when multiple tasks are in progress at the same time — not necessarily running simultaneously, but progressing independently. Go makes concurrency simple and efficient with goroutines and channels.
What Is a Goroutine?
A goroutine is a lightweight thread managed by the Go runtime. You start one with the go keyword:
go doSomething()
Characteristics:
- Very lightweight (~2KB initial stack)
- Managed by Go runtime, not the OS
- Automatically grows/shrinks its stack
- Scheduled by Go's internal M:N scheduler (many goroutines on few OS threads)
Real-World Scenario: Web Server Handling Requests
Code Example:
func handler(w http.ResponseWriter, r *http.Request) {
go logRequest(r) // non-blocking logging
data := process(r) // main request logic
w.Write(data)
}
What Happens:
- logRequest runs in a separate goroutine → doesn't block response
- process(r) runs on main goroutine handling the request
- Go's scheduler assigns both to available threads
Under the Hood: What Happens When a Goroutine Runs?
Go's Scheduler (G-M-P Model):
Go uses an internal M:N scheduler with three components:
- G: Goroutine (your code)
- M: Machine (OS thread)
- P: Processor (context for scheduling goroutines)
Lifecycle:
- You call
go doSomething()
- Go runtime:
- Creates a new G (goroutine struct)
- Adds it to a local run queue of a P
- A running M picks it up from P and runs it
- If doSomething() blocks (e.g., on IO or time.Sleep), Go parks the G, and the M picks another G
How Goroutines Differ from Threads
Feature | Goroutine | OS Thread |
---|---|---|
Memory | ~2KB stack | ~1MB stack |
Managed by | Go runtime | OS Kernel |
Scheduling | M:N, cooperative | 1:1, preemptive |
Startup cost | Tiny | Large |
Context switch | Fast (no syscall) | Slower (involves kernel) |
What Makes Go Concurrency Powerful?
- Built-in scheduler ensures fairness and efficiency
- Goroutines auto-grow/shrink stack
- Go has network poller (epoll/kqueue) to handle blocking IO without blocking the thread
- Garbage collector knows about goroutines and stack frames
Important Notes
- Blocking a goroutine ≠ blocking a thread: e.g., a goroutine waiting on a channel doesn't block an OS thread
- Too many goroutines? Still okay — Go can handle millions if you avoid leaks and deep blocking
- Use runtime.NumGoroutine() to inspect count
- Use pprof or go tool trace to profile concurrent programs
Conclusion & Best Practices
- Use goroutines for anything parallel (IO, background jobs, async tasks)
- Don’t forget to sync: use channels, WaitGroups, or mutexes
- Avoid goroutine leaks (e.g., waiting forever on a channel)
- Monitor memory and goroutine counts in production
Top comments (0)