0% completed
Concurrency and Parallelism
On This Page
Concurrency
Parallelism
Cores Available to a Go Program
A Concurrent Program on One Core
Parallel Execution
Comparing the Two
A Common Mistake
Using This in an Interview
Key Takeaways
A cook in a small kitchen is making soup and rice. The cook does not stand and watch the soup while it simmers. Instead, the cook stirs the soup, starts the rice, returns to the soup, and checks the rice again. Both dishes are in progress over the same half hour, even though only one pair of hands is working.
A kitchen with two cooks works differently. One cook stays with the soup and the other stays with the rice, so at any given second both dishes are receiving work. The first kitchen handles two dishes by switching between them, while the second kitchen does two pieces of work at the same instant.
Go uses two separate words for these two kitchens. This page defines both words, shows a Go program that behaves like each kitchen, and explains why the first kind of program needs only one CPU core.
Concurrency
Organizing a program as several tasks that each make progress over the same period of time is called concurrency (dealing with many tasks by switching between them). A concurrent program does not require the tasks to run at the same instant. It requires only that a task which is waiting does not stop the other tasks from moving forward.
In Go, each task is usually a goroutine, which is a function started with the go keyword that runs independently of the code that started it. The Go runtime switches a CPU core from one goroutine to another, most often when the current goroutine has to wait. A goroutine waits when it sleeps, when it reads from the network or a disk, or when it sends to or receives from a channel that is not ready.
Concurrency is a property of how the program is written. The same concurrent program can run on one core or on sixteen cores without any change to its code.
Parallelism
Executing several tasks at the same instant is called parallelism (doing many tasks at once on several CPU cores). Parallelism needs hardware with more than one core, because a single core executes the instructions of only one task at a time. Parallelism is therefore a property of how the program is run, not of how it is written.
A concurrent Go program becomes parallel when the runtime has more than one core available and more than one goroutine has work to do. The goroutines are the same in both cases. What changes is whether their instructions overlap in time.
Cores Available to a Go Program
Two values in the runtime package describe the cores a program may use. The function runtime.NumCPU returns the number of logical CPUs the process can use. A logical CPU is one stream of instructions that the hardware can run; a core that runs two such streams counts as two logical CPUs. The function runtime.GOMAXPROCS sets the maximum number of CPUs that can execute Go code at the same time, and it returns the previous setting.
One possible output (measured on one computer with 8 cores and 16 logical CPUs):
logical CPUs: 16 GOMAXPROCS: 16
By default, GOMAXPROCS follows the number of logical CPUs, so a program on this computer can run Go code on 16 logical CPUs at the same time. Since Go 1.25, the default on Linux also takes into account any CPU limit placed on a container. You rarely need to change the value. Setting it to 1, however, is a useful way to see concurrency with no parallelism at all.
A Concurrent Program on One Core
The program below models the first kitchen. Each dish is a goroutine, and each step of a dish is a sleep, which stands in for waiting while the soup simmers. The soup has three steps of 100 ms, and the rice has two steps of 140 ms. The call runtime.GOMAXPROCS(1) allows only one core to run Go code, so the two goroutines can never execute instructions at the same instant.
Output:
soup step 1 done at 100ms rice step 1 done at 140ms soup step 2 done at 200ms rice step 2 done at 280ms soup step 3 done at 300ms both dishes ready at 300ms
The steps of the two dishes alternate, and both dishes are ready at 300 ms. Cooked one after the other, they would need 300 ms for the soup plus 280 ms for the rice, which is 580 ms. The main function receives from done twice, so it waits until both goroutines have reported that they finished.
The single core was idle for almost all of those 300 ms. While the soup goroutine sleeps, it needs no CPU, so the runtime runs the rice goroutine. When the rice goroutine sleeps as well, the runtime has nothing to run until one of the sleeps ends. This is why concurrency helps a program that spends most of its time waiting, even on one core.
Parallel Execution
Remove the call to runtime.GOMAXPROCS(1) and the program prints the same lines with the same times. The two goroutines may now run on different cores. However, each one uses very little CPU time per step, so the extra cores have almost nothing to do. Parallelism adds speed only when several goroutines need the CPU for long stretches at the same time.
A goroutine that adds up a billion numbers is an example of such work. It never waits, so switching between two of them on one core gives each one half of the core and finishes no sooner. Two such goroutines on two cores, by contrast, can finish in about half the time.
Comparing the Two
| Concurrency | Parallelism | |
|---|---|---|
| Meaning | Many tasks make progress over the same period | Many tasks execute at the same instant |
| Property of | How the program is written | How the program is run |
| Needs more than one core | No | Yes |
| Helps most when tasks | Spend time waiting | Spend time computing |
| In Go | Goroutines and channels | Goroutines on several cores, up to GOMAXPROCS |
A Common Mistake
A frequent mistake is to assume that starting goroutines makes a program faster. Goroutines make a program concurrent, and they let it overlap waiting time. They make it faster at computation only when free cores are available to run them. Even then, each goroutine needs enough work to justify the cost of starting it.
The opposite mistake also appears. Some engineers assume that a concurrent program is safe from timing problems if GOMAXPROCS is 1. It is not. The runtime can still switch between goroutines at many points, so two goroutines that share data can still interfere with each other on a single core.
Using This in an Interview
Concurrency is dealing with many tasks over the same period by switching between them, and parallelism is executing many tasks at the same instant on several cores. A Go program written with goroutines is concurrent, and it runs in parallel when more than one core is available. A likely follow-up asks whether a concurrent program can be useful on one core. It can, when its tasks spend most of their time waiting, because the core runs one task while the others wait.
Key Takeaways
- Concurrency means several tasks make progress over the same period, with the runtime switching between them.
- Parallelism means several tasks execute at the same instant, which needs more than one CPU core.
runtime.NumCPUreports the logical CPUs available, andruntime.GOMAXPROCS(0)reports how many may run Go code at once.- A concurrent program on one core still finishes sooner when its tasks spend their time waiting.
- More cores help only work that keeps the CPU busy.
Concurrency describes how a program is organized: as separate tasks that can wait without stopping each other. Parallelism describes how the hardware runs those tasks. A Go program runs in parallel whenever more than one core is available and more than one goroutine has work ready.
Reading Progress
0%
On This Page
Concurrency
Parallelism
Cores Available to a Go Program
A Concurrent Program on One Core
Parallel Execution
Comparing the Two
A Common Mistake
Using This in an Interview
Key Takeaways