DEV Community

Huseyn
Huseyn

Posted on

Concurrency & Goroutine in GO

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()
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)