DEV Community

Cover image for Why Does Go Have Goroutines? Understanding Go’s Lightweight Concurrency
Ashitosh Lavhate
Ashitosh Lavhate

Posted on

Why Does Go Have Goroutines? Understanding Go’s Lightweight Concurrency

When I started exploring Go, one feature immediately stood out:

go doSomething()
Enter fullscreen mode Exit fullscreen mode

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

The only difference is this:

go sayHello()
Enter fullscreen mode Exit fullscreen mode

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

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

Here:

  • wg.Add(2) tells the WaitGroup to wait for 2 tasks.
  • go starts each function as a goroutine.
  • defer wg.Done() signals that a task has finished.
  • wg.Wait() prevents main() 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()
Enter fullscreen mode Exit fullscreen mode

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)