When I started exploring Go, one feature immediately stood out:
go doSomething()
Just adding go before a function call can run it concurrently.
That got me thinking: what exactly is a goroutine, and why does Go need it?
So, I decided to learn the basics. Here's what I found. 👇
First, what is concurrency?
Imagine you have multiple tasks:
- Fetching data from an API
- Processing a file
- Handling user requests
- Writing data to a database
If your program handles them one after another, one task may spend a lot of time waiting while everything else is blocked.
Concurrency allows a program to make progress on multiple tasks during overlapping periods of time.
This is especially useful when programs need to handle lots of work efficiently.
What is a goroutine?
A goroutine is a function that runs concurrently with other goroutines.
Creating one is surprisingly simple:
package main
import "fmt"
func sayHello() {
fmt.Println("Hello from a goroutine!")
}
func main() {
go sayHello()
fmt.Println("Hello from main!")
}
The only difference is this:
go sayHello()
The go keyword tells Go to start sayHello() as a goroutine.
But there's a problem 👀
If you run the previous example, you might not always see:
Hello from a goroutine!
Why?
Because once main() finishes, the program exits.
The goroutine may not get enough time to complete.
A simple way to wait for goroutines is using sync.WaitGroup.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println("Task 1 completed")
}()
go func() {
defer wg.Done()
fmt.Println("Task 2 completed")
}()
wg.Wait()
fmt.Println("All tasks completed")
}
Here:
-
wg.Add(2)tells the WaitGroup to wait for 2 tasks. -
gostarts each function as a goroutine. -
defer wg.Done()signals that a task has finished. -
wg.Wait()preventsmain()from exiting until both tasks are complete.
Why not just create multiple threads?
This is where Go becomes particularly interesting.
Goroutines are not simply traditional OS threads.
They are lightweight units of concurrent execution managed by the Go runtime. The runtime schedules goroutines onto OS threads, which means you can work with large numbers of goroutines without manually creating and managing a thread for every task.
That's one of the reasons Go is popular for systems and services that need to handle many concurrent operations.
Concurrency vs Parallelism
One thing I also learned while reading about goroutines:
Concurrency and parallelism are not exactly the same thing.
- Concurrency: Structuring a program so multiple tasks can make progress independently.
- Parallelism: Actually executing multiple tasks at the same time using multiple processing resources.
A program can be concurrent without every task literally running at the exact same instant.
The bigger picture
Goroutines become even more powerful when combined with channels, which allow goroutines to communicate and synchronize.
So the Go concurrency model is not just:
"Run everything at the same time."
It's about making concurrent programs easier to structure using tools built directly into the language and its runtime.
What I learned today
My biggest takeaway was this:
Goroutines make concurrency feel simple, but Go's runtime is doing a lot of work behind the scenes to manage them efficiently.
What starts with:
go doSomething()
opens the door to learning about scheduling, synchronization, channels, race conditions, parallelism, and scalable systems.
I'm still learning Go, but this was a really interesting first step into understanding how it handles concurrency. 🚀
What Go concept should I explore next: Channels, Interfaces, defer, or something else?
Top comments (0)