Why is goroutine being called a “lightweight” thread?

Jessie Kuo
3 min readMar 31, 2022

I was asked this question in one of my recent software engineer job interviews, and I believe I didn’t answer it as well as I would have liked, so I wanted to write this blog to re-answer it and perhaps aid someone else who is also curious about the solution.

In Go articles, we often see goroutine introduced as a lightweight thread. But why is it lightweight? how lightweight?

Before we delve into the “lightweight” part, we need to first understand the two components in this comparison equation —Thread & Goroutine.

What is a thread?

In short, a thread is a basic unit of CPU utilization; it comprises a thread ID, a program counter, a register set, and a stack. It shares with other threads belonging to the same process its code section, data section, and other operating-system resources, such as open files and signals.

What is a goroutine?

In Go, a “go” statement starts the execution of a function call as an independent concurrent thread of control, or goroutine, within the same address space.

The expression must be a function or method call; it cannot be parenthesized. Calls of built-in functions are restricted as for expression statements.

func main() {
go fmt.Println("Hello from another goroutine")
fmt.Println("Hello from the main goroutine")

// At this point the program execution stops and all
// active goroutines are killed.
}

The function value and parameters are evaluated as usual in the calling goroutine, but unlike with a regular call, program execution does not wait for the invoked function to complete. Instead, the function begins executing independently in a new goroutine. When the function terminates, its goroutine also terminates. If the function has any return values, they are discarded when the function completes.

Goroutine vs. Thread

Each operating system thread has a fixed-size block memory (sometimes as large as 2MB) for its stack, which is the work area where it saves the local variables of function calls that are in process or momentarily halted while another function is performed. This fixed-size stack is both too big and too small. A 2MB stack would be a tremendous waste of memory for a small goroutine that simply waits for a WaitGroup before closing a channel.

It is not uncommon for a Go program to generate hundreds of thousands of goroutines at once, which would be difficult to stack. Regardless of size, fixed-size stacks are not always large enough for the most complex and deeply recursive routines. Changing the fixed size can improve space efficiency and allow for the creation of more threads, or it can permit more deeply recursive algorithms, but not both.

A goroutine, on the other hand, starts with a modest stack, typically 2KB. The stack of a goroutine, like the stack of an OS thread, maintains the local variable of active and suspended function calls, but unlike the stack of an OS thread, the stack of a goroutine is not fixed; it grows and shrinks as needed. A goroutine stack’s size limit could be as much as 1GB, which is orders of magnitude larger than a conventional fixed-size thread stack; however, few goroutines use that much.

Wrapping Up

In Summary, to answer the question of why goroutine is called a lightweight thread. It’s because a goroutine starts with a stack space of 2KB which is extremely smaller and more compact than OS thread's fixed-size stack space of 2MB. However, the goroutine’s stack space is growable and it can grow and exceed the OS thread’s 2MB fixed-stack size. So a goroutine is actually only “lightweight” in the beginning and could gradually grow “overweight” when needed!

Thank you for reading!

Check out my website for more content: https://yeunyuankuo.github.io/

--

--